wip: basic impl of file notification no log rotate but might now do it 🤷

This commit is contained in:
Laurence 2024-04-07 12:37:19 +01:00
parent 990dd5e08e
commit f50700429c
No known key found for this signature in database
GPG key ID: B053BEE3478E8FEF
4 changed files with 206 additions and 0 deletions

View file

@ -0,0 +1,17 @@
ifeq ($(OS), Windows_NT)
SHELL := pwsh.exe
.SHELLFLAGS := -NoProfile -Command
EXT = .exe
endif
GO = go
GOBUILD = $(GO) build
BINARY_NAME = notification-file$(EXT)
build: clean
$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME)
.PHONY: clean
clean:
@$(RM) $(BINARY_NAME) $(WIN_IGNORE_ERR)

View file

@ -0,0 +1,25 @@
# Don't change this
type: file
name: file_default # this must match with the registered plugin in the profile
log_level: info # Options include: trace, debug, info, warn, error, off
# This template render all events as ndjson
format: |
{{range . -}}
{ "time": "{{.StopAt}}", "program": "crowdsec", "alert": {{. | toJson }} }
{{ end -}}
# group_wait: # duration to wait collecting alerts before sending to this plugin, eg "30s"
# group_threshold: # if alerts exceed this, then the plugin will be sent the message. eg "10"
#Use full path EG /tmp/crowdsec_alerts.json or %TEMP%\crowdsec_alerts.json
log_path: "/tmp/crowdsec_alerts.json"
rotate:
enabled: true # Change to false if you want to handle log rotate on system basis
max_size: 500
max_files: 5
max_age: 5
compress: true
log_format:
custom_format: "%msg%" # https://github.com/t-tomalak/logrus-easy-formatter

View file

@ -0,0 +1,160 @@
package main
import (
"context"
"fmt"
"os"
"sync"
"time"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"gopkg.in/yaml.v2"
)
type PluginConfig struct {
Name string `yaml:"name"`
LogLevel string `yaml:"log_level"`
LogPath string `yaml:"log_path"`
WriteChan chan string `yaml:"-"`
FileWriter *os.File `yaml:"-"`
LogRotate LogRotate `yaml:"rotate"`
}
type LogRotate struct {
MaxSize int `yaml:"max_size"`
MaxAge int `yaml:"max_age"`
MaxFiles int `yaml:"max_files"`
Enabled bool `yaml:"enabled"`
Compress bool `yaml:"compress"`
}
type FilePlugin struct {
PluginConfigByName map[string]PluginConfig
}
var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
Name: "file-plugin",
Level: hclog.LevelFromString("INFO"),
Output: os.Stderr,
JSONFormat: true,
})
func Monit(cfg PluginConfig) {
logger.Debug("Starting monit")
queue := make([]string, 0)
queueMutex := &sync.Mutex{}
ticker := time.NewTicker(5 * time.Second)
if cfg.FileWriter == nil {
cfg.FileWriter, _ = os.OpenFile(cfg.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
for {
select {
case <-ticker.C:
logger.Debug("Checking queue")
if len(queue) == 0 {
logger.Debug("Queue is empty")
continue
}
logger.Debug(fmt.Sprintf("Writing %d logs to file", len(queue)))
newQueue := make([]string, 0, len(queue))
originalFileInfo, err := cfg.FileWriter.Stat()
if err != nil {
logger.Error("Failed to get file info", "error", err)
}
for _, log := range queue {
var err error
currentFileInfo, _ := os.Stat(cfg.LogPath)
// Check if the file writer is still pointing to the same file
if !os.SameFile(originalFileInfo, currentFileInfo) {
// The file has been rotated
logger.Info("Log file has been rotated or missing attempting to reopen it")
cfg.FileWriter.Close()
cfg.FileWriter, err = os.OpenFile(cfg.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
logger.Error("Failed to reopen log file", "error", err)
newQueue = append(newQueue, log)
continue
}
logger.Info("Log file has been reopened successfully")
originalFileInfo, _ = cfg.FileWriter.Stat()
}
_, err = cfg.FileWriter.WriteString(log + "\n")
if err != nil {
logger.Error("Failed to write log", "error", err)
newQueue = append(newQueue, log)
}
}
cfg.FileWriter.Sync()
queueMutex.Lock()
queue = newQueue
queueMutex.Unlock()
// TODO! Implement log rotation
// if cfg.LogRotate.Enabled {
// // check if file size is greater than max size
// fileInfo, _ := cfg.FileWriter.Stat()
// if fileInfo.Size() > int64(cfg.LogRotate.MaxSize) {
// // close file
// cfg.FileWriter.Close()
// // rename file
// os.Rename(cfg.LogPath, cfg.LogPath+".1")
// // open file
// cfg.FileWriter, _ = os.OpenFile(cfg.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// // compress file
// if cfg.LogRotate.Compress {
// // compress file
// }
// }
// }
case log := <-cfg.WriteChan:
logger.Trace("Received log", log)
queueMutex.Lock()
queue = append(queue, log)
queueMutex.Unlock()
}
}
}
func (s *FilePlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
if _, ok := s.PluginConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
}
cfg := s.PluginConfigByName[notification.Name]
go func() {
cfg.WriteChan <- notification.Text
}()
return &protobufs.Empty{}, nil
}
func (s *FilePlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
d := PluginConfig{}
err := yaml.Unmarshal(config.Config, &d)
d.WriteChan = make(chan string)
s.PluginConfigByName[d.Name] = d
logger.SetLevel(hclog.LevelFromString(d.LogLevel))
go Monit(d)
return &protobufs.Empty{}, err
}
func main() {
var handshake = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
}
sp := &FilePlugin{PluginConfigByName: make(map[string]PluginConfig)}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshake,
Plugins: map[string]plugin.Plugin{
"file": &protobufs.NotifierPlugin{
Impl: sp,
},
},
GRPCServer: plugin.DefaultGRPCServer,
Logger: logger,
})
}

View file

@ -82,12 +82,14 @@ SLACK_PLUGIN_BINARY="./cmd/notification-slack/notification-slack"
SPLUNK_PLUGIN_BINARY="./cmd/notification-splunk/notification-splunk"
EMAIL_PLUGIN_BINARY="./cmd/notification-email/notification-email"
SENTINEL_PLUGIN_BINARY="./cmd/notification-sentinel/notification-sentinel"
FILE_PLUGIN_BINARY="./cmd/notification-file/notification-file"
HTTP_PLUGIN_CONFIG="./cmd/notification-http/http.yaml"
SLACK_PLUGIN_CONFIG="./cmd/notification-slack/slack.yaml"
SPLUNK_PLUGIN_CONFIG="./cmd/notification-splunk/splunk.yaml"
EMAIL_PLUGIN_CONFIG="./cmd/notification-email/email.yaml"
SENTINEL_PLUGIN_CONFIG="./cmd/notification-sentinel/sentinel.yaml"
FILE_PLUGIN_CONFIG="./cmd/notification-file/file.yaml"
BACKUP_DIR=$(mktemp -d)
@ -523,6 +525,7 @@ install_plugins(){
cp ${HTTP_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
cp ${EMAIL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
cp ${SENTINEL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
cp ${FILE_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
if [[ ${DOCKER_MODE} == "false" ]]; then
cp -n ${SLACK_PLUGIN_CONFIG} /etc/crowdsec/notifications/
@ -530,6 +533,7 @@ install_plugins(){
cp -n ${HTTP_PLUGIN_CONFIG} /etc/crowdsec/notifications/
cp -n ${EMAIL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
cp -n ${SENTINEL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
cp -n ${FILE_PLUGIN_CONFIG} /etc/crowdsec/notifications/
fi
}