From 5dcb467842f9316edf9bab54f87c0455425d8c01 Mon Sep 17 00:00:00 2001 From: Vomitblood Date: Sun, 9 Feb 2025 17:40:04 +0800 Subject: [PATCH] modularize telegram bot and log watcher --- server/internal/log_watcher/log_watcher.go | 98 ++++++++++++++++++++++ server/internal/telegram/telegram.go | 88 +------------------ 2 files changed, 101 insertions(+), 85 deletions(-) create mode 100644 server/internal/log_watcher/log_watcher.go diff --git a/server/internal/log_watcher/log_watcher.go b/server/internal/log_watcher/log_watcher.go new file mode 100644 index 0000000..7d111ba --- /dev/null +++ b/server/internal/log_watcher/log_watcher.go @@ -0,0 +1,98 @@ +package log_watcher + +import ( + "bufio" + "encoding/json" + "log" + "os" + + "github.com/Vomitblood/cspj-application/server/internal/telegram" + "github.com/fsnotify/fsnotify" + tg "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +const ( + // TODO: add config for user to specify log location + modsecLogFile = "/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-auditlog.log" +) + +var lastReadPosition int64 = 0 + +type LogEntry struct { + AuditData struct { + Messages []string `json:"messages"` + } `json:"audit_data"` +} + +func watchLogFile(bot *tg.BotAPI) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal("Failed to initialize watcher:", err) + } + defer watcher.Close() + + // add log file to watcher + err = watcher.Add(modsecLogFile) + if err != nil { + log.Fatal("Failed to watch log file:", err) + } + + log.Println("Monitoring log file for changes...") + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("Log file updated, reading new entries...") + readNewLines(bot) + } + + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("Watcher error:", err) + } + } +} + +func readNewLines(bot *tg.BotAPI) { + file, err := os.Open(modsecLogFile) + if err != nil { + log.Println("Failed to reopen log file:", err) + return + } + defer file.Close() + + // move to the last read position + file.Seek(lastReadPosition, os.SEEK_SET) + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + var logEntry LogEntry + + // try to parse json + if err := json.Unmarshal([]byte(line), &logEntry); err != nil { + log.Println("⚠️ Failed to parse JSON:", err) + // skip invalid json lines + // very crucial as modsecurity does not respect the json spec + continue + } + + // send index 0 element will do + if len(logEntry.AuditData.Messages) > 0 { + telegram.SendTelegramAlert(bot, logEntry.AuditData.Messages[0]) + } + } + + // update last read position + lastReadPosition, _ = file.Seek(0, os.SEEK_CUR) + + if err := scanner.Err(); err != nil { + log.Println("Error reading log file:", err) + } +} diff --git a/server/internal/telegram/telegram.go b/server/internal/telegram/telegram.go index ac4f9a1..7cb4283 100644 --- a/server/internal/telegram/telegram.go +++ b/server/internal/telegram/telegram.go @@ -1,32 +1,24 @@ package telegram import ( - "bufio" - "encoding/json" "fmt" "log" - "os" - "github.com/fsnotify/fsnotify" tg "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) const ( - // TODO: add config for user to specify log location - modsecLogFile = "/home/vomitblood/build/cspj-application/docker/chungus/logs/host-fs-auditlog.log" telegramToken = "7215466800:AAGwjZnXEfbbjQiA0y7qtSzbSZNUWQJnyjo" telegramChatID = 622943829 ) -var lastReadPosition int64 = 0 - type LogEntry struct { AuditData struct { Messages []string `json:"messages"` } `json:"audit_data"` } -func TelegramBotInit() { +func TelegramBotInit() (*tg.BotAPI, error) { bot, err := tg.NewBotAPI(telegramToken) if err != nil { log.Fatal("Failed to create Telegram bot:", err) @@ -41,84 +33,10 @@ func TelegramBotInit() { log.Fatal("Failed to send test message:", err) } - // start watching the log file for changes - watchLogFile(bot) + return bot, nil } -func watchLogFile(bot *tg.BotAPI) { - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal("Failed to initialize watcher:", err) - } - defer watcher.Close() - - // add log file to watcher - err = watcher.Add(modsecLogFile) - if err != nil { - log.Fatal("Failed to watch log file:", err) - } - - log.Println("Monitoring log file for changes...") - - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write == fsnotify.Write { - log.Println("Log file updated, reading new entries...") - readNewLines(bot) - } - - case err, ok := <-watcher.Errors: - if !ok { - return - } - log.Println("Watcher error:", err) - } - } -} - -func readNewLines(bot *tg.BotAPI) { - file, err := os.Open(modsecLogFile) - if err != nil { - log.Println("Failed to reopen log file:", err) - return - } - defer file.Close() - - // move to the last read position - file.Seek(lastReadPosition, os.SEEK_SET) - scanner := bufio.NewScanner(file) - - for scanner.Scan() { - line := scanner.Text() - var logEntry LogEntry - - // try to parse json - if err := json.Unmarshal([]byte(line), &logEntry); err != nil { - log.Println("⚠️ Failed to parse JSON:", err) - // skip invalid json lines - // very crucial as modsecurity does not respect the json spec - continue - } - - // send index 0 element will do - if len(logEntry.AuditData.Messages) > 0 { - sendTelegramAlert(bot, logEntry.AuditData.Messages[0]) - } - } - - // update last read position - lastReadPosition, _ = file.Seek(0, os.SEEK_CUR) - - if err := scanner.Err(); err != nil { - log.Println("Error reading log file:", err) - } -} - -func sendTelegramAlert(bot *tg.BotAPI, message string) { +func SendTelegramAlert(bot *tg.BotAPI, message string) { msg := tg.NewMessage(telegramChatID, fmt.Sprintf("🚨 *WEEWOO ALERT* 🚨\n%s", message)) _, err := bot.Send(msg) if err != nil {