modularize telegram bot and log watcher

This commit is contained in:
Vomitblood 2025-02-09 17:40:04 +08:00
parent d57c6ea6aa
commit 5dcb467842
2 changed files with 101 additions and 85 deletions

View file

@ -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)
}
}

View file

@ -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 {