wip: basic impl of file notification no log rotate but might now do it 🤷
This commit is contained in:
parent
990dd5e08e
commit
f50700429c
17
cmd/notification-file/Makefile
Normal file
17
cmd/notification-file/Makefile
Normal 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)
|
25
cmd/notification-file/file.yaml
Normal file
25
cmd/notification-file/file.yaml
Normal 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
|
160
cmd/notification-file/main.go
Normal file
160
cmd/notification-file/main.go
Normal 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,
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue