Compare commits
9 commits
master
...
allow_cont
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a3c055a08 | ||
![]() |
1eaaa2075d | ||
![]() |
c7614a327a | ||
![]() |
97300548f7 | ||
![]() |
c8fee2a656 | ||
![]() |
3561c4de3a | ||
![]() |
a3f7f966d0 | ||
![]() |
281fbd739f | ||
![]() |
4932a832fa |
11 changed files with 381 additions and 173 deletions
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -216,24 +217,33 @@ func NewLapiCmd() *cobra.Command {
|
|||
return cmdLapi
|
||||
}
|
||||
|
||||
func AddContext(key string, values []string) error {
|
||||
func AddContext(key string, values []string, targetFile string) error {
|
||||
if err := exprhelpers.Init(nil); err != nil {
|
||||
return fmt.Errorf(err.Error())
|
||||
}
|
||||
|
||||
if err := alertcontext.ValidateContextExpr(key, values); err != nil {
|
||||
return fmt.Errorf("invalid context configuration :%s", err)
|
||||
}
|
||||
if _, ok := csConfig.Crowdsec.ContextToSend[key]; !ok {
|
||||
csConfig.Crowdsec.ContextToSend[key] = make([]string, 0)
|
||||
log.Infof("key '%s' added", key)
|
||||
}
|
||||
data := csConfig.Crowdsec.ContextToSend[key]
|
||||
for _, val := range values {
|
||||
if !slices.Contains(data, val) {
|
||||
log.Infof("value '%s' added to key '%s'", val, key)
|
||||
data = append(data, val)
|
||||
|
||||
for _, ctx := range csConfig.Crowdsec.ContextToSend {
|
||||
if ctx.SourceFile == targetFile {
|
||||
if _, ok := ctx.Context[key]; !ok {
|
||||
ctx.Context[key] = make([]string, 0)
|
||||
log.Infof("key '%s' added", key)
|
||||
}
|
||||
data := ctx.Context[key]
|
||||
for _, val := range values {
|
||||
if !slices.Contains(data, val) {
|
||||
log.Infof("value '%s' added to key '%s'", val, key)
|
||||
data = append(data, val)
|
||||
}
|
||||
ctx.Context[key] = data
|
||||
}
|
||||
if err := csConfig.Crowdsec.DumpContextConfigFile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
csConfig.Crowdsec.ContextToSend[key] = data
|
||||
}
|
||||
if err := csConfig.Crowdsec.DumpContextConfigFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -246,7 +256,7 @@ func NewLapiContextCmd() *cobra.Command {
|
|||
DisableAutoGenTag: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := csConfig.LoadCrowdsec(); err != nil {
|
||||
fileNotFoundMessage := fmt.Sprintf("failed to open context file: open %s: no such file or directory", csConfig.Crowdsec.ConsoleContextPath)
|
||||
fileNotFoundMessage := fmt.Sprintf("failed to open context file: open %s: no such file or directory", csConfig.Crowdsec.ContextPath)
|
||||
if err.Error() != fileNotFoundMessage {
|
||||
log.Fatalf("Unable to load CrowdSec Agent: %s", err)
|
||||
}
|
||||
|
@ -264,6 +274,7 @@ func NewLapiContextCmd() *cobra.Command {
|
|||
|
||||
var keyToAdd string
|
||||
var valuesToAdd []string
|
||||
var filetoAdd string
|
||||
cmdContextAdd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add context to send with alerts. You must specify the output key with the expr value you want",
|
||||
|
@ -273,8 +284,21 @@ cscli lapi context add --value evt.Meta.source_ip --value evt.Meta.target_user
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if filetoAdd == "" {
|
||||
filetoAdd = csConfig.Crowdsec.ContextPath
|
||||
}
|
||||
|
||||
// check if the provided file is the context path file or in the configured context directory
|
||||
fileInFolder, err := isFilePathInFolder(csConfig.Crowdsec.ContextDir, filetoAdd)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to get relative path from '%s' and '%s'", filetoAdd, csConfig.Crowdsec.ContextDir)
|
||||
}
|
||||
if filetoAdd != csConfig.Crowdsec.ContextPath && !fileInFolder {
|
||||
log.Fatalf("file to edit ('%s') must be '%s' or in the folder '%s/'", filetoAdd, csConfig.Crowdsec.ContextPath, csConfig.Crowdsec.ContextDir)
|
||||
}
|
||||
|
||||
if keyToAdd != "" {
|
||||
if err := AddContext(keyToAdd, valuesToAdd); err != nil {
|
||||
if err := AddContext(keyToAdd, valuesToAdd, filetoAdd); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
return
|
||||
|
@ -284,13 +308,14 @@ cscli lapi context add --value evt.Meta.source_ip --value evt.Meta.target_user
|
|||
keySlice := strings.Split(v, ".")
|
||||
key := keySlice[len(keySlice)-1]
|
||||
value := []string{v}
|
||||
if err := AddContext(key, value); err != nil {
|
||||
if err := AddContext(key, value, filetoAdd); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdContextAdd.Flags().StringVarP(&keyToAdd, "key", "k", "", "The key of the different values to send")
|
||||
cmdContextAdd.Flags().StringVarP(&filetoAdd, "file", "f", "", "Filepath to add context")
|
||||
cmdContextAdd.Flags().StringSliceVar(&valuesToAdd, "value", []string{}, "The expr fields to associate with the key")
|
||||
cmdContextAdd.MarkFlagRequired("value")
|
||||
cmdContext.AddCommand(cmdContextAdd)
|
||||
|
@ -300,18 +325,28 @@ cscli lapi context add --value evt.Meta.source_ip --value evt.Meta.target_user
|
|||
Short: "List context to send with alerts",
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(csConfig.Crowdsec.ContextToSend) == 0 {
|
||||
fmt.Println("No context found on this agent. You can use 'cscli lapi context add' to add context to your alerts.")
|
||||
return
|
||||
contextFound := false
|
||||
for _, ctx := range csConfig.Crowdsec.ContextToSend {
|
||||
if len(ctx.Context) == 0 {
|
||||
continue
|
||||
}
|
||||
contextFound = true
|
||||
var dump []byte
|
||||
var err error
|
||||
if csConfig.Cscli.Output == "json" {
|
||||
dump, err = json.MarshalIndent(ctx, "", " ")
|
||||
} else {
|
||||
dump, err = yaml.Marshal(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("unable to show context status: %s", err)
|
||||
}
|
||||
fmt.Println(string(dump))
|
||||
}
|
||||
|
||||
dump, err := yaml.Marshal(csConfig.Crowdsec.ContextToSend)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to show context status: %s", err)
|
||||
if !contextFound {
|
||||
log.Fatalf("No context found for this agent.")
|
||||
}
|
||||
|
||||
fmt.Println(string(dump))
|
||||
|
||||
},
|
||||
}
|
||||
cmdContext.AddCommand(cmdContextStatus)
|
||||
|
@ -407,6 +442,7 @@ cscli lapi context detect crowdsecurity/sshd-logs
|
|||
|
||||
var keysToDelete []string
|
||||
var valuesToDelete []string
|
||||
var fileToDelete string
|
||||
cmdContextDelete := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete context to send with alerts",
|
||||
|
@ -419,40 +455,61 @@ cscli lapi context delete --value evt.Line.Src
|
|||
log.Fatalf("please provide at least a key or a value to delete")
|
||||
}
|
||||
|
||||
for _, key := range keysToDelete {
|
||||
if _, ok := csConfig.Crowdsec.ContextToSend[key]; ok {
|
||||
delete(csConfig.Crowdsec.ContextToSend, key)
|
||||
log.Infof("key '%s' has been removed", key)
|
||||
} else {
|
||||
log.Warningf("key '%s' doesn't exist", key)
|
||||
}
|
||||
if fileToDelete == "" {
|
||||
fileToDelete = csConfig.Crowdsec.ContextPath
|
||||
}
|
||||
|
||||
for _, value := range valuesToDelete {
|
||||
valueFound := false
|
||||
for key, context := range csConfig.Crowdsec.ContextToSend {
|
||||
if slices.Contains(context, value) {
|
||||
valueFound = true
|
||||
csConfig.Crowdsec.ContextToSend[key] = removeFromSlice(value, context)
|
||||
log.Infof("value '%s' has been removed from key '%s'", value, key)
|
||||
}
|
||||
if len(csConfig.Crowdsec.ContextToSend[key]) == 0 {
|
||||
delete(csConfig.Crowdsec.ContextToSend, key)
|
||||
// check if the provided file is the context path file or in the configured context directory
|
||||
fileInFolder, err := isFilePathInFolder(csConfig.Crowdsec.ContextDir, fileToDelete)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to get relative path from '%s' and '%s'", fileToDelete, csConfig.Crowdsec.ContextDir)
|
||||
}
|
||||
if fileToDelete != csConfig.Crowdsec.ContextPath && !fileInFolder {
|
||||
log.Fatalf("file to edit ('%s') must be '%s' or in the folder '%s/'", fileToDelete, csConfig.Crowdsec.ContextPath, csConfig.Crowdsec.ContextDir)
|
||||
}
|
||||
|
||||
for _, ctx := range csConfig.Crowdsec.ContextToSend {
|
||||
if ctx.SourceFile != fileToDelete {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, key := range keysToDelete {
|
||||
if _, ok := ctx.Context[key]; ok {
|
||||
delete(ctx.Context, key)
|
||||
log.Infof("key '%s' has been removed", key)
|
||||
} else {
|
||||
log.Warningf("key '%s' doesn't exist", key)
|
||||
}
|
||||
}
|
||||
if !valueFound {
|
||||
log.Warningf("value '%s' not found", value)
|
||||
|
||||
for _, value := range valuesToDelete {
|
||||
valueFound := false
|
||||
for key, context := range ctx.Context {
|
||||
if slices.Contains(context, value) {
|
||||
valueFound = true
|
||||
ctx.Context[key] = removeFromSlice(value, context)
|
||||
log.Infof("value '%s' has been removed from key '%s'", value, key)
|
||||
}
|
||||
if len(ctx.Context[key]) == 0 {
|
||||
delete(ctx.Context, key)
|
||||
}
|
||||
}
|
||||
if !valueFound {
|
||||
log.Warningf("value '%s' not found", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := csConfig.Crowdsec.DumpContextConfigFile(); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
if err := csConfig.Crowdsec.DumpContextConfigFile(ctx); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdContextDelete.Flags().StringSliceVarP(&keysToDelete, "key", "k", []string{}, "The keys to delete")
|
||||
cmdContextDelete.Flags().StringSliceVar(&valuesToDelete, "value", []string{}, "The expr fields to delete")
|
||||
cmdContextDelete.Flags().StringVarP(&fileToDelete, "file", "f", "", "Filepath to add context")
|
||||
cmdContext.AddCommand(cmdContextDelete)
|
||||
|
||||
return cmdContext
|
||||
|
|
|
@ -9,16 +9,17 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/agext/levenshtein"
|
||||
"github.com/fatih/color"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/prom2json"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/agext/levenshtein"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
|
@ -688,10 +689,10 @@ var ranges = []unit{
|
|||
{value: 1e18, symbol: "E"},
|
||||
{value: 1e15, symbol: "P"},
|
||||
{value: 1e12, symbol: "T"},
|
||||
{value: 1e9, symbol: "G"},
|
||||
{value: 1e6, symbol: "M"},
|
||||
{value: 1e3, symbol: "k"},
|
||||
{value: 1, symbol: ""},
|
||||
{value: 1e9, symbol: "G"},
|
||||
{value: 1e6, symbol: "M"},
|
||||
{value: 1e3, symbol: "k"},
|
||||
{value: 1, symbol: ""},
|
||||
}
|
||||
|
||||
func formatNumber(num int) string {
|
||||
|
@ -746,3 +747,16 @@ func removeFromSlice(val string, slice []string) []string {
|
|||
return slice
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
isFilePathInFolder uses filepath.Rel() to obtain a relative path
|
||||
from basePath to targetPath. If the relative path contains "..",
|
||||
then targetPath is not a sub-path of basePath
|
||||
*/
|
||||
func isFilePathInFolder(folderpath string, targetFilepath string) (bool, error) {
|
||||
relPath, err := filepath.Rel(folderpath, targetFilepath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !strings.Contains(relPath, ".."), nil
|
||||
}
|
||||
|
|
1
debian/rules
vendored
1
debian/rules
vendored
|
@ -21,6 +21,7 @@ override_dh_auto_install:
|
|||
mkdir -p debian/crowdsec/etc/crowdsec/hub/
|
||||
mkdir -p debian/crowdsec/usr/share/crowdsec/config
|
||||
mkdir -p debian/crowdsec/etc/crowdsec/console/
|
||||
mkdir -p debian/crowdsec/etc/crowdsec/context/
|
||||
|
||||
mkdir -p debian/crowdsec/usr/lib/crowdsec/plugins/
|
||||
mkdir -p debian/crowdsec/etc/crowdsec/notifications/
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -40,7 +42,7 @@ func ValidateContextExpr(key string, expressions []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewAlertContext(contextToSend map[string][]string, valueLength int) error {
|
||||
func NewAlertContext(contextToSend []csconfig.ContextToSend, valueLength int) error {
|
||||
var clog = log.New()
|
||||
if err := types.ConfigureLogger(clog); err != nil {
|
||||
return fmt.Errorf("couldn't create logger for alert context: %s", err)
|
||||
|
@ -56,20 +58,36 @@ func NewAlertContext(contextToSend map[string][]string, valueLength int) error {
|
|||
}
|
||||
|
||||
alertContext = Context{
|
||||
ContextToSend: contextToSend,
|
||||
ContextToSend: make(map[string][]string),
|
||||
ContextValueLen: valueLength,
|
||||
Log: clog,
|
||||
ContextToSendCompiled: make(map[string][]*vm.Program),
|
||||
}
|
||||
|
||||
for key, values := range contextToSend {
|
||||
alertContext.ContextToSendCompiled[key] = make([]*vm.Program, 0)
|
||||
for _, value := range values {
|
||||
valueCompiled, err := expr.Compile(value, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compilation of '%s' context value failed: %v", value, err)
|
||||
for _, ctx := range contextToSend {
|
||||
for key, values := range ctx.Context {
|
||||
if _, ok := alertContext.ContextToSend[key]; !ok {
|
||||
alertContext.ContextToSend[key] = make([]string, 0)
|
||||
}
|
||||
|
||||
if _, ok := alertContext.ContextToSendCompiled[key]; !ok {
|
||||
alertContext.ContextToSendCompiled[key] = make([]*vm.Program, 0)
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
if slices.Contains(alertContext.ContextToSend[key], value) {
|
||||
log.Debugf("value '%s' from '%s' already in context", value, ctx.SourceFile)
|
||||
continue
|
||||
}
|
||||
|
||||
valueCompiled, err := expr.Compile(value, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compilation of '%s' context value failed: %v", value, err)
|
||||
}
|
||||
|
||||
alertContext.ContextToSendCompiled[key] = append(alertContext.ContextToSendCompiled[key], valueCompiled)
|
||||
alertContext.ContextToSend[key] = append(alertContext.ContextToSend[key], value)
|
||||
}
|
||||
alertContext.ContextToSendCompiled[key] = append(alertContext.ContextToSendCompiled[key], valueCompiled)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,24 +2,34 @@ package alertcontext
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewAlertContext(t *testing.T) {
|
||||
contextFileFullPath, err := filepath.Abs("./tests/context.yaml")
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
name string
|
||||
contextToSend map[string][]string
|
||||
contextToSend []csconfig.ContextToSend
|
||||
valueLength int
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "basic config test",
|
||||
contextToSend: map[string][]string{
|
||||
"test": {"evt.Parsed.source_ip"},
|
||||
contextToSend: []csconfig.ContextToSend{
|
||||
csconfig.ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
},
|
||||
},
|
||||
},
|
||||
valueLength: 100,
|
||||
expectedErr: nil,
|
||||
|
@ -35,18 +45,26 @@ func TestNewAlertContext(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEventToContext(t *testing.T) {
|
||||
contextFileFullPath, err := filepath.Abs("./tests/context.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
contextToSend map[string][]string
|
||||
contextToSend []csconfig.ContextToSend
|
||||
valueLength int
|
||||
events []types.Event
|
||||
expectedResult models.Meta
|
||||
}{
|
||||
{
|
||||
name: "basic test",
|
||||
contextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"nonexistent_field": {"evt.Parsed.nonexist"},
|
||||
contextToSend: []csconfig.ContextToSend{
|
||||
csconfig.ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"nonexistent_field": {"evt.Parsed.nonexist"},
|
||||
},
|
||||
},
|
||||
},
|
||||
valueLength: 100,
|
||||
events: []types.Event{
|
||||
|
@ -66,10 +84,14 @@ func TestEventToContext(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "test many events",
|
||||
contextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"cve": {"evt.Parsed.cve"},
|
||||
contextToSend: []csconfig.ContextToSend{
|
||||
csconfig.ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"cve": {"evt.Parsed.cve"}},
|
||||
},
|
||||
},
|
||||
valueLength: 100,
|
||||
events: []types.Event{
|
||||
|
@ -112,11 +134,17 @@ func TestEventToContext(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "test many events with result above max length (need truncate, keep only 2 on 3 elements)",
|
||||
contextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"uri": {"evt.Parsed.uri"},
|
||||
contextToSend: []csconfig.ContextToSend{
|
||||
csconfig.ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"uri": {"evt.Parsed.uri"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
valueLength: 100,
|
||||
events: []types.Event{
|
||||
{
|
||||
|
@ -158,10 +186,15 @@ func TestEventToContext(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "test one events with result above max length (need truncate on one element)",
|
||||
contextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"uri": {"evt.Parsed.uri"},
|
||||
contextToSend: []csconfig.ContextToSend{
|
||||
csconfig.ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
"source_machine": {"evt.Parsed.source_machine"},
|
||||
"uri": {"evt.Parsed.uri"},
|
||||
},
|
||||
},
|
||||
},
|
||||
valueLength: 100,
|
||||
events: []types.Event{
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
@ -11,29 +12,35 @@ import (
|
|||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
)
|
||||
|
||||
type ContextToSend struct {
|
||||
SourceFile string `yaml:"filepath" json:"filepath"`
|
||||
Context map[string][]string `yaml:"context" json:"context"`
|
||||
}
|
||||
|
||||
// CrowdsecServiceCfg contains the location of parsers/scenarios/... and acquisition files
|
||||
type CrowdsecServiceCfg struct {
|
||||
Enable *bool `yaml:"enable"`
|
||||
AcquisitionFilePath string `yaml:"acquisition_path,omitempty"`
|
||||
AcquisitionDirPath string `yaml:"acquisition_dir,omitempty"`
|
||||
ConsoleContextPath string `yaml:"console_context_path"`
|
||||
ConsoleContextValueLength int `yaml:"console_context_value_length"`
|
||||
AcquisitionFiles []string `yaml:"-"`
|
||||
ParserRoutinesCount int `yaml:"parser_routines"`
|
||||
BucketsRoutinesCount int `yaml:"buckets_routines"`
|
||||
OutputRoutinesCount int `yaml:"output_routines"`
|
||||
SimulationConfig *SimulationConfig `yaml:"-"`
|
||||
LintOnly bool `yaml:"-"` // if set to true, exit after loading configs
|
||||
BucketStateFile string `yaml:"state_input_file,omitempty"` // if we need to unserialize buckets at start
|
||||
BucketStateDumpDir string `yaml:"state_output_dir,omitempty"` // if we need to unserialize buckets on shutdown
|
||||
BucketsGCEnabled bool `yaml:"-"` // we need to garbage collect buckets when in forensic mode
|
||||
Enable *bool `yaml:"enable"`
|
||||
AcquisitionFilePath string `yaml:"acquisition_path,omitempty"`
|
||||
AcquisitionDirPath string `yaml:"acquisition_dir,omitempty"`
|
||||
ContextPath string `yaml:"console_context_path"`
|
||||
ContextDir string `yaml:"console_context_dir"`
|
||||
ContextValueLength int `yaml:"console_context_value_length"`
|
||||
AcquisitionFiles []string `yaml:"-"`
|
||||
ParserRoutinesCount int `yaml:"parser_routines"`
|
||||
BucketsRoutinesCount int `yaml:"buckets_routines"`
|
||||
OutputRoutinesCount int `yaml:"output_routines"`
|
||||
SimulationConfig *SimulationConfig `yaml:"-"`
|
||||
LintOnly bool `yaml:"-"` // if set to true, exit after loading configs
|
||||
BucketStateFile string `yaml:"state_input_file,omitempty"` // if we need to unserialize buckets at start
|
||||
BucketStateDumpDir string `yaml:"state_output_dir,omitempty"` // if we need to unserialize buckets on shutdown
|
||||
BucketsGCEnabled bool `yaml:"-"` // we need to garbage collect buckets when in forensic mode
|
||||
|
||||
HubDir string `yaml:"-"`
|
||||
DataDir string `yaml:"-"`
|
||||
ConfigDir string `yaml:"-"`
|
||||
HubIndexFile string `yaml:"-"`
|
||||
SimulationFilePath string `yaml:"-"`
|
||||
ContextToSend map[string][]string `yaml:"-"`
|
||||
HubDir string `yaml:"-"`
|
||||
DataDir string `yaml:"-"`
|
||||
ConfigDir string `yaml:"-"`
|
||||
HubIndexFile string `yaml:"-"`
|
||||
SimulationFilePath string `yaml:"-"`
|
||||
ContextToSend []ContextToSend `yaml:"-"`
|
||||
}
|
||||
|
||||
func (c *Config) LoadCrowdsec() error {
|
||||
|
@ -149,21 +156,21 @@ func (c *Config) LoadCrowdsec() error {
|
|||
return fmt.Errorf("while loading hub: %w", err)
|
||||
}
|
||||
|
||||
c.Crowdsec.ContextToSend = make(map[string][]string, 0)
|
||||
c.Crowdsec.ContextToSend = make([]ContextToSend, 0)
|
||||
fallback := false
|
||||
if c.Crowdsec.ConsoleContextPath == "" {
|
||||
if c.Crowdsec.ContextPath == "" {
|
||||
// fallback to default config file
|
||||
c.Crowdsec.ConsoleContextPath = filepath.Join(c.Crowdsec.ConfigDir, "console", "context.yaml")
|
||||
c.Crowdsec.ContextPath = filepath.Join(c.Crowdsec.ConfigDir, "console", "context.yaml")
|
||||
fallback = true
|
||||
}
|
||||
|
||||
f, err := filepath.Abs(c.Crowdsec.ConsoleContextPath)
|
||||
f, err := filepath.Abs(c.Crowdsec.ContextPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get absolute path of %s: %s", c.Crowdsec.ConsoleContextPath, err)
|
||||
return fmt.Errorf("fail to get absolute path of %s: %s", c.Crowdsec.ContextPath, err)
|
||||
}
|
||||
|
||||
c.Crowdsec.ConsoleContextPath = f
|
||||
yamlFile, err := os.ReadFile(c.Crowdsec.ConsoleContextPath)
|
||||
c.Crowdsec.ContextPath = f
|
||||
yamlFile, err := os.ReadFile(c.Crowdsec.ContextPath)
|
||||
if err != nil {
|
||||
if fallback {
|
||||
log.Debugf("Default context config file doesn't exist, will not use it")
|
||||
|
@ -171,28 +178,76 @@ func (c *Config) LoadCrowdsec() error {
|
|||
return fmt.Errorf("failed to open context file: %s", err)
|
||||
}
|
||||
} else {
|
||||
err = yaml.Unmarshal(yamlFile, c.Crowdsec.ContextToSend)
|
||||
ctxToSend := ContextToSend{
|
||||
SourceFile: c.Crowdsec.ContextPath,
|
||||
Context: make(map[string][]string),
|
||||
}
|
||||
err = yaml.Unmarshal(yamlFile, ctxToSend.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling labels console config file '%s': %s", c.Crowdsec.ConsoleContextPath, err)
|
||||
return fmt.Errorf("unmarshaling context config file '%s': %s", c.Crowdsec.ContextPath, err)
|
||||
}
|
||||
c.Crowdsec.ContextToSend = append(c.Crowdsec.ContextToSend, ctxToSend)
|
||||
}
|
||||
|
||||
if c.Crowdsec.ContextDir == "" {
|
||||
// fallback to default config file
|
||||
c.Crowdsec.ContextDir = filepath.Join(c.Crowdsec.ConfigDir, "context")
|
||||
}
|
||||
|
||||
// if context folder exist, try to read it
|
||||
if _, err := os.Stat(c.Crowdsec.ContextDir); !os.IsNotExist(err) {
|
||||
err := filepath.Walk(c.Crowdsec.ContextDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(path, ".yaml") && !strings.HasSuffix(path, ".yml") {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get absolute path of %s: %s", path, err)
|
||||
}
|
||||
|
||||
yamlFile, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read context file '%s': %s", f, err.Error())
|
||||
}
|
||||
ctxToSend := ContextToSend{
|
||||
SourceFile: f,
|
||||
Context: make(map[string][]string),
|
||||
}
|
||||
err = yaml.Unmarshal(yamlFile, ctxToSend.Context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal context file '%s': %s", f, err)
|
||||
}
|
||||
log.Debugf("Reading %s context file: %+v", f, string(yamlFile))
|
||||
|
||||
c.Crowdsec.ContextToSend = append(c.Crowdsec.ContextToSend, ctxToSend)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list context directory: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CrowdsecServiceCfg) DumpContextConfigFile() error {
|
||||
func (c *CrowdsecServiceCfg) DumpContextConfigFile(contextToSend ContextToSend) error {
|
||||
var out []byte
|
||||
var err error
|
||||
|
||||
if out, err = yaml.Marshal(c.ContextToSend); err != nil {
|
||||
return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", c.ConsoleContextPath, err)
|
||||
if out, err = yaml.Marshal(contextToSend.Context); err != nil {
|
||||
return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", contextToSend.SourceFile, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(c.ConsoleContextPath, out, 0600); err != nil {
|
||||
return fmt.Errorf("while dumping console config to %s: %w", c.ConsoleContextPath, err)
|
||||
if err := os.WriteFile(contextToSend.SourceFile, out, 0600); err != nil {
|
||||
return fmt.Errorf("while dumping console config to %s: %w", contextToSend.SourceFile, err)
|
||||
}
|
||||
|
||||
log.Infof("%s file saved", c.ConsoleContextPath)
|
||||
log.Infof("%s file saved", contextToSend.SourceFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ func TestLoadCrowdsec(t *testing.T) {
|
|||
contextFileFullPath, err := filepath.Abs("./tests/context.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
contextDirFullPath, err := filepath.Abs("./tests/context/")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *Config
|
||||
|
@ -56,29 +59,35 @@ func TestLoadCrowdsec(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Crowdsec: &CrowdsecServiceCfg{
|
||||
AcquisitionFilePath: "./tests/acquis.yaml",
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
ConsoleContextPath: "./tests/context.yaml",
|
||||
ConsoleContextValueLength: 2500,
|
||||
AcquisitionFilePath: "./tests/acquis.yaml",
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
ContextPath: "./tests/context.yaml",
|
||||
ContextValueLength: 2500,
|
||||
},
|
||||
},
|
||||
expectedResult: &CrowdsecServiceCfg{
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: "",
|
||||
ConsoleContextPath: contextFileFullPath,
|
||||
AcquisitionFilePath: acquisFullPath,
|
||||
ConfigDir: configDirFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ConsoleContextValueLength: 2500,
|
||||
AcquisitionFiles: []string{acquisFullPath},
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
ContextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: "",
|
||||
ContextPath: contextFileFullPath,
|
||||
ContextDir: contextDirFullPath,
|
||||
AcquisitionFilePath: acquisFullPath,
|
||||
ConfigDir: configDirFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ContextValueLength: 2500,
|
||||
AcquisitionFiles: []string{acquisFullPath},
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
ContextToSend: []ContextToSend{
|
||||
ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
},
|
||||
},
|
||||
},
|
||||
SimulationConfig: &SimulationConfig{
|
||||
Simulation: ptr.Of(false),
|
||||
|
@ -102,25 +111,31 @@ func TestLoadCrowdsec(t *testing.T) {
|
|||
AcquisitionFilePath: "./tests/acquis.yaml",
|
||||
AcquisitionDirPath: "./tests/acquis/",
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
ConsoleContextPath: "./tests/context.yaml",
|
||||
ContextPath: "./tests/context.yaml",
|
||||
},
|
||||
},
|
||||
expectedResult: &CrowdsecServiceCfg{
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: acquisDirFullPath,
|
||||
AcquisitionFilePath: acquisFullPath,
|
||||
ConsoleContextPath: contextFileFullPath,
|
||||
ConfigDir: configDirFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ConsoleContextValueLength: 0,
|
||||
AcquisitionFiles: []string{acquisFullPath, acquisInDirFullPath},
|
||||
ContextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: acquisDirFullPath,
|
||||
AcquisitionFilePath: acquisFullPath,
|
||||
ContextPath: contextFileFullPath,
|
||||
ContextDir: contextDirFullPath,
|
||||
ConfigDir: configDirFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ContextValueLength: 0,
|
||||
AcquisitionFiles: []string{acquisFullPath, acquisInDirFullPath},
|
||||
ContextToSend: []ContextToSend{
|
||||
ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
},
|
||||
},
|
||||
},
|
||||
SimulationFilePath: "./tests/simulation.yaml",
|
||||
SimulationConfig: &SimulationConfig{
|
||||
|
@ -142,27 +157,33 @@ func TestLoadCrowdsec(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Crowdsec: &CrowdsecServiceCfg{
|
||||
ConsoleContextPath: contextFileFullPath,
|
||||
ConsoleContextValueLength: 10,
|
||||
ContextPath: contextFileFullPath,
|
||||
ContextValueLength: 10,
|
||||
},
|
||||
},
|
||||
expectedResult: &CrowdsecServiceCfg{
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: "",
|
||||
AcquisitionFilePath: "",
|
||||
ConfigDir: configDirFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
ConsoleContextPath: contextFileFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ConsoleContextValueLength: 10,
|
||||
AcquisitionFiles: []string{},
|
||||
SimulationFilePath: "",
|
||||
ContextToSend: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
Enable: ptr.Of(true),
|
||||
AcquisitionDirPath: "",
|
||||
AcquisitionFilePath: "",
|
||||
ConfigDir: configDirFullPath,
|
||||
HubIndexFile: hubIndexFileFullPath,
|
||||
DataDir: dataFullPath,
|
||||
HubDir: hubFullPath,
|
||||
ContextPath: contextFileFullPath,
|
||||
BucketsRoutinesCount: 1,
|
||||
ParserRoutinesCount: 1,
|
||||
OutputRoutinesCount: 1,
|
||||
ContextValueLength: 10,
|
||||
ContextDir: contextDirFullPath,
|
||||
AcquisitionFiles: []string{},
|
||||
SimulationFilePath: "",
|
||||
ContextToSend: []ContextToSend{
|
||||
ContextToSend{
|
||||
SourceFile: contextFileFullPath,
|
||||
Context: map[string][]string{
|
||||
"source_ip": {"evt.Parsed.source_ip"},
|
||||
},
|
||||
},
|
||||
},
|
||||
SimulationConfig: &SimulationConfig{
|
||||
Simulation: ptr.Of(false),
|
||||
|
@ -183,7 +204,7 @@ func TestLoadCrowdsec(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Crowdsec: &CrowdsecServiceCfg{
|
||||
ConsoleContextPath: "",
|
||||
ContextPath: "",
|
||||
AcquisitionFilePath: "./tests/acquis_not_exist.yaml",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -264,7 +264,7 @@ func LoadBuckets(cscfg *csconfig.CrowdsecServiceCfg, files []string, tomb *tomb.
|
|||
}
|
||||
}
|
||||
|
||||
if err := alertcontext.NewAlertContext(cscfg.ContextToSend, cscfg.ConsoleContextValueLength); err != nil {
|
||||
if err := alertcontext.NewAlertContext(cscfg.ContextToSend, cscfg.ContextValueLength); err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to load alert context: %s", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ rm -rf %{buildroot}
|
|||
mkdir -p %{buildroot}/etc/crowdsec/hub
|
||||
mkdir -p %{buildroot}/etc/crowdsec/patterns
|
||||
mkdir -p %{buildroot}/etc/crowdsec/console/
|
||||
mkdir -p %{buildroot}/etc/crowdsec/context/
|
||||
mkdir -p %{buildroot}%{_sharedstatedir}/%{name}/data
|
||||
mkdir -p %{buildroot}%{_presetdir}
|
||||
|
||||
|
|
|
@ -59,6 +59,11 @@
|
|||
<File Id="context.yaml" Source="config\context.yaml" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="ContextDir" Name="context">
|
||||
<Component Id="CreateContextDir" Guid="fa136232-a8cc-496e-86f3-578d7d1e619c">
|
||||
<CreateFolder />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Component Id="OnlineCreds" Guid="a652a6cb-d464-40b1-8f50-78dce0135d20">
|
||||
<File Id="online_api_credentials.yaml" Source="config\online_api_credentials.yaml">
|
||||
<PermissionEx Sddl="D:PAI(A;;FA;;;SY)(A;;FA;;;BA)"/>
|
||||
|
@ -172,6 +177,7 @@
|
|||
<ComponentRef Id="NotifConfig" />
|
||||
<ComponentRef Id="CreateCrowdsecPluginsDir"/>
|
||||
<ComponentRef Id="CreateCrowdsecDataDir" />
|
||||
<ComponentRef Id="CreateContextDir" />
|
||||
<ComponentRef Id="ConsoleContextFile"/>
|
||||
<ComponentRef Id="Csconfig_lapi" />
|
||||
<ComponentRef Id="Csconfig_no_lapi" />
|
||||
|
|
|
@ -29,6 +29,7 @@ CROWDSEC_LOG_FILE="/var/log/crowdsec.log"
|
|||
LAPI_LOG_FILE="/var/log/crowdsec_api.log"
|
||||
CROWDSEC_PLUGIN_DIR="${CROWDSEC_USR_DIR}/plugins"
|
||||
CROWDSEC_CONSOLE_DIR="${CROWDSEC_PATH}/console"
|
||||
CROWDSEC_CONTEXT_DIR="${CROWDSEC_PATH}/context"
|
||||
|
||||
CROWDSEC_BIN="./cmd/crowdsec/crowdsec"
|
||||
CSCLI_BIN="./cmd/crowdsec-cli/cscli"
|
||||
|
@ -415,6 +416,7 @@ install_crowdsec() {
|
|||
mkdir -p "${CROWDSEC_CONFIG_PATH}/collections" || exit
|
||||
mkdir -p "${CROWDSEC_CONFIG_PATH}/patterns" || exit
|
||||
mkdir -p "${CROWDSEC_CONSOLE_DIR}" || exit
|
||||
mkdir -p "${CROWDSEC_CONTEXT_DIR}" || exit
|
||||
|
||||
#tmp
|
||||
mkdir -p /tmp/data
|
||||
|
|
Loading…
Add table
Reference in a new issue