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"
|
SPLUNK_PLUGIN_BINARY="./cmd/notification-splunk/notification-splunk"
|
||||||
EMAIL_PLUGIN_BINARY="./cmd/notification-email/notification-email"
|
EMAIL_PLUGIN_BINARY="./cmd/notification-email/notification-email"
|
||||||
SENTINEL_PLUGIN_BINARY="./cmd/notification-sentinel/notification-sentinel"
|
SENTINEL_PLUGIN_BINARY="./cmd/notification-sentinel/notification-sentinel"
|
||||||
|
FILE_PLUGIN_BINARY="./cmd/notification-file/notification-file"
|
||||||
|
|
||||||
HTTP_PLUGIN_CONFIG="./cmd/notification-http/http.yaml"
|
HTTP_PLUGIN_CONFIG="./cmd/notification-http/http.yaml"
|
||||||
SLACK_PLUGIN_CONFIG="./cmd/notification-slack/slack.yaml"
|
SLACK_PLUGIN_CONFIG="./cmd/notification-slack/slack.yaml"
|
||||||
SPLUNK_PLUGIN_CONFIG="./cmd/notification-splunk/splunk.yaml"
|
SPLUNK_PLUGIN_CONFIG="./cmd/notification-splunk/splunk.yaml"
|
||||||
EMAIL_PLUGIN_CONFIG="./cmd/notification-email/email.yaml"
|
EMAIL_PLUGIN_CONFIG="./cmd/notification-email/email.yaml"
|
||||||
SENTINEL_PLUGIN_CONFIG="./cmd/notification-sentinel/sentinel.yaml"
|
SENTINEL_PLUGIN_CONFIG="./cmd/notification-sentinel/sentinel.yaml"
|
||||||
|
FILE_PLUGIN_CONFIG="./cmd/notification-file/file.yaml"
|
||||||
|
|
||||||
|
|
||||||
BACKUP_DIR=$(mktemp -d)
|
BACKUP_DIR=$(mktemp -d)
|
||||||
|
@ -523,6 +525,7 @@ install_plugins(){
|
||||||
cp ${HTTP_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
cp ${HTTP_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
||||||
cp ${EMAIL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
cp ${EMAIL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
||||||
cp ${SENTINEL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
cp ${SENTINEL_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
||||||
|
cp ${FILE_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR}
|
||||||
|
|
||||||
if [[ ${DOCKER_MODE} == "false" ]]; then
|
if [[ ${DOCKER_MODE} == "false" ]]; then
|
||||||
cp -n ${SLACK_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
cp -n ${SLACK_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
||||||
|
@ -530,6 +533,7 @@ install_plugins(){
|
||||||
cp -n ${HTTP_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
cp -n ${HTTP_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
||||||
cp -n ${EMAIL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
cp -n ${EMAIL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
||||||
cp -n ${SENTINEL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
cp -n ${SENTINEL_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
||||||
|
cp -n ${FILE_PLUGIN_CONFIG} /etc/crowdsec/notifications/
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue