|
@@ -7,6 +7,9 @@ import (
|
|
|
"os"
|
|
|
"strings"
|
|
|
|
|
|
+ "github.com/antonmedv/expr"
|
|
|
+ "github.com/antonmedv/expr/vm"
|
|
|
+ "github.com/google/uuid"
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
tomb "gopkg.in/tomb.v2"
|
|
@@ -23,6 +26,7 @@ import (
|
|
|
s3acquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/s3"
|
|
|
syslogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog"
|
|
|
wineventlogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/wineventlog"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
|
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
|
@@ -30,16 +34,17 @@ import (
|
|
|
|
|
|
// The interface each datasource must implement
|
|
|
type DataSource interface {
|
|
|
- GetMetrics() []prometheus.Collector // Returns pointers to metrics that are managed by the module
|
|
|
- GetAggregMetrics() []prometheus.Collector // Returns pointers to metrics that are managed by the module (aggregated mode, limits cardinality)
|
|
|
- UnmarshalConfig([]byte) error // Decode and pre-validate the YAML datasource - anything that can be checked before runtime
|
|
|
- Configure([]byte, *log.Entry) error // Complete the YAML datasource configuration and perform runtime checks.
|
|
|
- ConfigureByDSN(string, map[string]string, *log.Entry) error // Configure the datasource
|
|
|
- GetMode() string // Get the mode (TAIL, CAT or SERVER)
|
|
|
- GetName() string // Get the name of the module
|
|
|
- OneShotAcquisition(chan types.Event, *tomb.Tomb) error // Start one shot acquisition(eg, cat a file)
|
|
|
- StreamingAcquisition(chan types.Event, *tomb.Tomb) error // Start live acquisition (eg, tail a file)
|
|
|
- CanRun() error // Whether the datasource can run or not (eg, journalctl on BSD is a non-sense)
|
|
|
+ GetMetrics() []prometheus.Collector // Returns pointers to metrics that are managed by the module
|
|
|
+ GetAggregMetrics() []prometheus.Collector // Returns pointers to metrics that are managed by the module (aggregated mode, limits cardinality)
|
|
|
+ UnmarshalConfig([]byte) error // Decode and pre-validate the YAML datasource - anything that can be checked before runtime
|
|
|
+ Configure([]byte, *log.Entry) error // Complete the YAML datasource configuration and perform runtime checks.
|
|
|
+ ConfigureByDSN(string, map[string]string, *log.Entry, string) error // Configure the datasource
|
|
|
+ GetMode() string // Get the mode (TAIL, CAT or SERVER)
|
|
|
+ GetName() string // Get the name of the module
|
|
|
+ OneShotAcquisition(chan types.Event, *tomb.Tomb) error // Start one shot acquisition(eg, cat a file)
|
|
|
+ StreamingAcquisition(chan types.Event, *tomb.Tomb) error // Start live acquisition (eg, tail a file)
|
|
|
+ CanRun() error // Whether the datasource can run or not (eg, journalctl on BSD is a non-sense)
|
|
|
+ GetUuid() string // Get the unique identifier of the datasource
|
|
|
Dump() interface{}
|
|
|
}
|
|
|
|
|
@@ -56,6 +61,8 @@ var AcquisitionSources = map[string]func() DataSource{
|
|
|
"s3": func() DataSource { return &s3acquisition.S3Source{} },
|
|
|
}
|
|
|
|
|
|
+var transformRuntimes = map[string]*vm.Program{}
|
|
|
+
|
|
|
func GetDataSourceIface(dataSourceType string) DataSource {
|
|
|
source := AcquisitionSources[dataSourceType]
|
|
|
if source == nil {
|
|
@@ -115,7 +122,7 @@ func detectBackwardCompatAcquis(sub configuration.DataSourceCommonCfg) string {
|
|
|
return ""
|
|
|
}
|
|
|
|
|
|
-func LoadAcquisitionFromDSN(dsn string, labels map[string]string) ([]DataSource, error) {
|
|
|
+func LoadAcquisitionFromDSN(dsn string, labels map[string]string, transformExpr string) ([]DataSource, error) {
|
|
|
var sources []DataSource
|
|
|
|
|
|
frags := strings.Split(dsn, ":")
|
|
@@ -134,7 +141,15 @@ func LoadAcquisitionFromDSN(dsn string, labels map[string]string) ([]DataSource,
|
|
|
subLogger := clog.WithFields(log.Fields{
|
|
|
"type": dsn,
|
|
|
})
|
|
|
- err := dataSrc.ConfigureByDSN(dsn, labels, subLogger)
|
|
|
+ uniqueId := uuid.NewString()
|
|
|
+ if transformExpr != "" {
|
|
|
+ vm, err := expr.Compile(transformExpr, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("while compiling transform expression '%s': %w", transformExpr, err)
|
|
|
+ }
|
|
|
+ transformRuntimes[uniqueId] = vm
|
|
|
+ }
|
|
|
+ err := dataSrc.ConfigureByDSN(dsn, labels, subLogger, uniqueId)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("while configuration datasource for %s: %w", dsn, err)
|
|
|
}
|
|
@@ -185,10 +200,19 @@ func LoadAcquisitionFromFile(config *csconfig.CrowdsecServiceCfg) ([]DataSource,
|
|
|
if GetDataSourceIface(sub.Source) == nil {
|
|
|
return nil, fmt.Errorf("unknown data source %s in %s (position: %d)", sub.Source, acquisFile, idx)
|
|
|
}
|
|
|
+ uniqueId := uuid.NewString()
|
|
|
+ sub.UniqueId = uniqueId
|
|
|
src, err := DataSourceConfigure(sub)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("while configuring datasource of type %s from %s (position: %d): %w", sub.Source, acquisFile, idx, err)
|
|
|
}
|
|
|
+ if sub.TransformExpr != "" {
|
|
|
+ vm, err := expr.Compile(sub.TransformExpr, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("while compiling transform expression '%s' for datasource %s in %s (position: %d): %w", sub.TransformExpr, sub.Source, acquisFile, idx, err)
|
|
|
+ }
|
|
|
+ transformRuntimes[uniqueId] = vm
|
|
|
+ }
|
|
|
sources = append(sources, *src)
|
|
|
idx += 1
|
|
|
}
|
|
@@ -212,11 +236,60 @@ func GetMetrics(sources []DataSource, aggregated bool) error {
|
|
|
// ignore the error
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func transform(transformChan chan types.Event, output chan types.Event, AcquisTomb *tomb.Tomb, transformRuntime *vm.Program, logger *log.Entry) {
|
|
|
+ defer types.CatchPanic("crowdsec/acquis")
|
|
|
+ logger.Infof("transformer started")
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case <-AcquisTomb.Dying():
|
|
|
+ logger.Debugf("transformer is dying")
|
|
|
+ return
|
|
|
+ case evt := <-transformChan:
|
|
|
+ logger.Tracef("Received event %s", evt.Line.Raw)
|
|
|
+ out, err := expr.Run(transformRuntime, map[string]interface{}{"evt": &evt})
|
|
|
+ if err != nil {
|
|
|
+ logger.Errorf("while running transform expression: %s, sending event as-is", err)
|
|
|
+ output <- evt
|
|
|
+ }
|
|
|
+ if out == nil {
|
|
|
+ logger.Errorf("transform expression returned nil, sending event as-is")
|
|
|
+ output <- evt
|
|
|
+ }
|
|
|
+ switch v := out.(type) {
|
|
|
+ case string:
|
|
|
+ logger.Tracef("transform expression returned %s", v)
|
|
|
+ evt.Line.Raw = v
|
|
|
+ output <- evt
|
|
|
+ case []interface{}:
|
|
|
+ logger.Tracef("transform expression returned %v", v) //nolint:asasalint // We actually want to log the slice content
|
|
|
+ for _, line := range v {
|
|
|
+ l, ok := line.(string)
|
|
|
+ if !ok {
|
|
|
+ logger.Errorf("transform expression returned []interface{}, but cannot assert an element to string")
|
|
|
+ output <- evt
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ evt.Line.Raw = l
|
|
|
+ output <- evt
|
|
|
+ }
|
|
|
+ case []string:
|
|
|
+ logger.Tracef("transform expression returned %v", v)
|
|
|
+ for _, line := range v {
|
|
|
+ evt.Line.Raw = line
|
|
|
+ output <- evt
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ logger.Errorf("transform expression returned an invalid type %T, sending event as-is", out)
|
|
|
+ output <- evt
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func StartAcquisition(sources []DataSource, output chan types.Event, AcquisTomb *tomb.Tomb) error {
|
|
|
for i := 0; i < len(sources); i++ {
|
|
|
subsrc := sources[i] //ensure its a copy
|
|
@@ -225,10 +298,26 @@ func StartAcquisition(sources []DataSource, output chan types.Event, AcquisTomb
|
|
|
AcquisTomb.Go(func() error {
|
|
|
defer types.CatchPanic("crowdsec/acquis")
|
|
|
var err error
|
|
|
+
|
|
|
+ outChan := output
|
|
|
+ log.Debugf("datasource %s UUID: %s", subsrc.GetName(), subsrc.GetUuid())
|
|
|
+ if transformRuntime, ok := transformRuntimes[subsrc.GetUuid()]; ok {
|
|
|
+ log.Infof("transform expression found for datasource %s", subsrc.GetName())
|
|
|
+ transformChan := make(chan types.Event)
|
|
|
+ outChan = transformChan
|
|
|
+ transformLogger := log.WithFields(log.Fields{
|
|
|
+ "component": "transform",
|
|
|
+ "datasource": subsrc.GetName(),
|
|
|
+ })
|
|
|
+ AcquisTomb.Go(func() error {
|
|
|
+ transform(outChan, output, AcquisTomb, transformRuntime, transformLogger)
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+ }
|
|
|
if subsrc.GetMode() == configuration.TAIL_MODE {
|
|
|
- err = subsrc.StreamingAcquisition(output, AcquisTomb)
|
|
|
+ err = subsrc.StreamingAcquisition(outChan, AcquisTomb)
|
|
|
} else {
|
|
|
- err = subsrc.OneShotAcquisition(output, AcquisTomb)
|
|
|
+ err = subsrc.OneShotAcquisition(outChan, AcquisTomb)
|
|
|
}
|
|
|
if err != nil {
|
|
|
//if one of the acqusition returns an error, we kill the others to properly shutdown
|