github.com/crowdsecurity/crowdsec@v1.6.1/pkg/parser/runtime.go (about)

     1  package parser
     2  
     3  /*
     4   This file contains
     5   - the runtime parsing routines
     6  */
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/mohae/deepcopy"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	log "github.com/sirupsen/logrus"
    20  
    21  	"github.com/crowdsecurity/crowdsec/pkg/dumps"
    22  	"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
    23  	"github.com/crowdsecurity/crowdsec/pkg/types"
    24  )
    25  
    26  /* ok, this is kinda experimental, I don't know how bad of an idea it is .. */
    27  func SetTargetByName(target string, value string, evt *types.Event) bool {
    28  	if evt == nil {
    29  		return false
    30  	}
    31  
    32  	//it's a hack, we do it for the user
    33  	target = strings.TrimPrefix(target, "evt.")
    34  
    35  	log.Debugf("setting target %s to %s", target, value)
    36  	defer func() {
    37  		if r := recover(); r != nil {
    38  			log.Errorf("Runtime error while trying to set '%s': %+v", target, r)
    39  			return
    40  		}
    41  	}()
    42  
    43  	iter := reflect.ValueOf(evt).Elem()
    44  	if (iter == reflect.Value{}) || iter.IsZero() {
    45  		log.Tracef("event is nill")
    46  		//event is nill
    47  		return false
    48  	}
    49  	for _, f := range strings.Split(target, ".") {
    50  		/*
    51  		** According to current Event layout we only have to handle struct and map
    52  		 */
    53  		switch iter.Kind() {
    54  		case reflect.Map:
    55  			tmp := iter.MapIndex(reflect.ValueOf(f))
    56  			/*if we're in a map and the field doesn't exist, the user wants to add it :) */
    57  			if (tmp == reflect.Value{}) || tmp.IsZero() {
    58  				log.Debugf("map entry is zero in '%s'", target)
    59  			}
    60  			iter.SetMapIndex(reflect.ValueOf(f), reflect.ValueOf(value))
    61  			return true
    62  		case reflect.Struct:
    63  			tmp := iter.FieldByName(f)
    64  			if !tmp.IsValid() {
    65  				log.Debugf("'%s' is not a valid target because '%s' is not valid", target, f)
    66  				return false
    67  			}
    68  			if tmp.Kind() == reflect.Ptr {
    69  				tmp = reflect.Indirect(tmp)
    70  			}
    71  			iter = tmp
    72  		case reflect.Ptr:
    73  			tmp := iter.Elem()
    74  			iter = reflect.Indirect(tmp.FieldByName(f))
    75  		default:
    76  			log.Errorf("unexpected type %s in '%s'", iter.Kind(), target)
    77  			return false
    78  		}
    79  	}
    80  	//now we should have the final member :)
    81  	if !iter.CanSet() {
    82  		log.Errorf("'%s' can't be set", target)
    83  		return false
    84  	}
    85  	if iter.Kind() != reflect.String {
    86  		log.Errorf("Expected string, got %v when handling '%s'", iter.Kind(), target)
    87  		return false
    88  	}
    89  	iter.Set(reflect.ValueOf(value))
    90  	return true
    91  }
    92  
    93  func printStaticTarget(static ExtraField) string {
    94  	switch {
    95  	case static.Method != "":
    96  		return static.Method
    97  	case static.Parsed != "":
    98  		return fmt.Sprintf(".Parsed[%s]", static.Parsed)
    99  	case static.Meta != "":
   100  		return fmt.Sprintf(".Meta[%s]", static.Meta)
   101  	case static.Enriched != "":
   102  		return fmt.Sprintf(".Enriched[%s]", static.Enriched)
   103  	case static.TargetByName != "":
   104  		return static.TargetByName
   105  	default:
   106  		return "?"
   107  	}
   108  }
   109  
   110  func (n *Node) ProcessStatics(statics []ExtraField, event *types.Event) error {
   111  	//we have a few cases :
   112  	//(meta||key) + (static||reference||expr)
   113  	var value string
   114  	clog := n.Logger
   115  
   116  	for _, static := range statics {
   117  		value = ""
   118  		if static.Value != "" {
   119  			value = static.Value
   120  		} else if static.RunTimeValue != nil {
   121  			output, err := exprhelpers.Run(static.RunTimeValue, map[string]interface{}{"evt": event}, clog, n.Debug)
   122  			if err != nil {
   123  				clog.Warningf("failed to run RunTimeValue : %v", err)
   124  				continue
   125  			}
   126  			switch out := output.(type) {
   127  			case string:
   128  				value = out
   129  			case int:
   130  				value = strconv.Itoa(out)
   131  			case float64, float32:
   132  				value = fmt.Sprintf("%f", out)
   133  			case map[string]interface{}:
   134  				clog.Warnf("Expression '%s' returned a map, please use ToJsonString() to convert it to string if you want to keep it as is, or refine your expression to extract a string", static.ExpValue)
   135  			case []interface{}:
   136  				clog.Warnf("Expression '%s' returned an array, please use ToJsonString() to convert it to string if you want to keep it as is, or refine your expression to extract a string", static.ExpValue)
   137  			case nil:
   138  				clog.Debugf("Expression '%s' returned nil, skipping", static.ExpValue)
   139  			default:
   140  				clog.Errorf("unexpected return type for '%s' : %T", static.ExpValue, output)
   141  				return errors.New("unexpected return type for RunTimeValue")
   142  			}
   143  		}
   144  
   145  		if value == "" {
   146  			//allow ParseDate to have empty input
   147  			if static.Method != "ParseDate" {
   148  				clog.Debugf("Empty value for %s, skip.", printStaticTarget(static))
   149  				continue
   150  			}
   151  		}
   152  
   153  		if static.Method != "" {
   154  			processed := false
   155  			/*still way too hackish, but : inject all the results in enriched, and */
   156  			if enricherPlugin, ok := n.EnrichFunctions.Registered[static.Method]; ok {
   157  				clog.Tracef("Found method '%s'", static.Method)
   158  				ret, err := enricherPlugin.EnrichFunc(value, event, enricherPlugin.Ctx, n.Logger.WithField("method", static.Method))
   159  				if err != nil {
   160  					clog.Errorf("method '%s' returned an error : %v", static.Method, err)
   161  				}
   162  				processed = true
   163  				clog.Debugf("+ Method %s('%s') returned %d entries to merge in .Enriched\n", static.Method, value, len(ret))
   164  				//Hackish check, but those methods do not return any data by design
   165  				if len(ret) == 0 && static.Method != "UnmarshalJSON" {
   166  					clog.Debugf("+ Method '%s' empty response on '%s'", static.Method, value)
   167  				}
   168  				for k, v := range ret {
   169  					clog.Debugf("\t.Enriched[%s] = '%s'\n", k, v)
   170  					event.Enriched[k] = v
   171  				}
   172  			} else {
   173  				clog.Debugf("method '%s' doesn't exist or plugin not initialized", static.Method)
   174  			}
   175  			if !processed {
   176  				clog.Debugf("method '%s' doesn't exist", static.Method)
   177  			}
   178  		} else if static.Parsed != "" {
   179  			clog.Debugf(".Parsed[%s] = '%s'", static.Parsed, value)
   180  			event.Parsed[static.Parsed] = value
   181  		} else if static.Meta != "" {
   182  			clog.Debugf(".Meta[%s] = '%s'", static.Meta, value)
   183  			event.Meta[static.Meta] = value
   184  		} else if static.Enriched != "" {
   185  			clog.Debugf(".Enriched[%s] = '%s'", static.Enriched, value)
   186  			event.Enriched[static.Enriched] = value
   187  		} else if static.TargetByName != "" {
   188  			if !SetTargetByName(static.TargetByName, value, event) {
   189  				clog.Errorf("Unable to set value of '%s'", static.TargetByName)
   190  			} else {
   191  				clog.Debugf("%s = '%s'", static.TargetByName, value)
   192  			}
   193  		} else {
   194  			clog.Fatal("unable to process static : unknown target")
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  var NodesHits = prometheus.NewCounterVec(
   201  	prometheus.CounterOpts{
   202  		Name: "cs_node_hits_total",
   203  		Help: "Total events entered node.",
   204  	},
   205  	[]string{"source", "type", "name"},
   206  )
   207  
   208  var NodesHitsOk = prometheus.NewCounterVec(
   209  	prometheus.CounterOpts{
   210  		Name: "cs_node_hits_ok_total",
   211  		Help: "Total events successfully exited node.",
   212  	},
   213  	[]string{"source", "type", "name"},
   214  )
   215  
   216  var NodesHitsKo = prometheus.NewCounterVec(
   217  	prometheus.CounterOpts{
   218  		Name: "cs_node_hits_ko_total",
   219  		Help: "Total events unsuccessfully exited node.",
   220  	},
   221  	[]string{"source", "type", "name"},
   222  )
   223  
   224  //
   225  
   226  var NodesWlHitsOk = prometheus.NewCounterVec(
   227  	prometheus.CounterOpts{
   228  		Name: "cs_node_wl_hits_ok_total",
   229  		Help: "Total events successfully whitelisted by node.",
   230  	},
   231  	[]string{"source", "type", "name", "reason"},
   232  )
   233  
   234  var NodesWlHits = prometheus.NewCounterVec(
   235  	prometheus.CounterOpts{
   236  		Name: "cs_node_wl_hits_total",
   237  		Help: "Total events processed by whitelist node.",
   238  	},
   239  	[]string{"source", "type", "name", "reason"},
   240  )
   241  
   242  func stageidx(stage string, stages []string) int {
   243  	for i, v := range stages {
   244  		if stage == v {
   245  			return i
   246  		}
   247  	}
   248  	return -1
   249  }
   250  
   251  var ParseDump bool
   252  var DumpFolder string
   253  
   254  var StageParseCache dumps.ParserResults
   255  var StageParseMutex sync.Mutex
   256  
   257  func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error) {
   258  	var event = xp
   259  
   260  	/* the stage is undefined, probably line is freshly acquired, set to first stage !*/
   261  	if event.Stage == "" && len(ctx.Stages) > 0 {
   262  		event.Stage = ctx.Stages[0]
   263  		log.Tracef("no stage, set to : %s", event.Stage)
   264  	}
   265  	event.Process = false
   266  	if event.Time.IsZero() {
   267  		event.Time = time.Now().UTC()
   268  	}
   269  
   270  	if event.Parsed == nil {
   271  		event.Parsed = make(map[string]string)
   272  	}
   273  	if event.Enriched == nil {
   274  		event.Enriched = make(map[string]string)
   275  	}
   276  	if event.Meta == nil {
   277  		event.Meta = make(map[string]string)
   278  	}
   279  	if event.Unmarshaled == nil {
   280  		event.Unmarshaled = make(map[string]interface{})
   281  	}
   282  	if event.Type == types.LOG {
   283  		log.Tracef("INPUT '%s'", event.Line.Raw)
   284  	}
   285  
   286  	if ParseDump {
   287  		if StageParseCache == nil {
   288  			StageParseMutex.Lock()
   289  			StageParseCache = make(dumps.ParserResults)
   290  			StageParseCache["success"] = make(map[string][]dumps.ParserResult)
   291  			StageParseCache["success"][""] = make([]dumps.ParserResult, 0)
   292  			StageParseMutex.Unlock()
   293  		}
   294  	}
   295  
   296  	for _, stage := range ctx.Stages {
   297  		if ParseDump {
   298  			StageParseMutex.Lock()
   299  			if _, ok := StageParseCache[stage]; !ok {
   300  				StageParseCache[stage] = make(map[string][]dumps.ParserResult)
   301  			}
   302  			StageParseMutex.Unlock()
   303  		}
   304  		/* if the node is forward in stages, seek to this stage */
   305  		/* this is for example used by testing system to inject logs in post-syslog-parsing phase*/
   306  		if stageidx(event.Stage, ctx.Stages) > stageidx(stage, ctx.Stages) {
   307  			log.Tracef("skipping stage, we are already at [%s] expecting [%s]", event.Stage, stage)
   308  			continue
   309  		}
   310  		log.Tracef("node stage : %s, current stage : %s", event.Stage, stage)
   311  
   312  		/* if the stage is wrong, it means that the log didn't manage "pass" a stage with a onsuccess: next_stage tag */
   313  		if event.Stage != stage {
   314  			log.Debugf("Event not parsed, expected stage '%s' got '%s', abort", stage, event.Stage)
   315  			event.Process = false
   316  			return event, nil
   317  		}
   318  
   319  		isStageOK := false
   320  		for idx, node := range nodes {
   321  			//Only process current stage's nodes
   322  			if event.Stage != node.Stage {
   323  				continue
   324  			}
   325  			clog := log.WithFields(log.Fields{
   326  				"node-name": node.rn,
   327  				"stage":     event.Stage,
   328  			})
   329  			clog.Tracef("Processing node %d/%d -> %s", idx, len(nodes), node.rn)
   330  			if ctx.Profiling {
   331  				node.Profiling = true
   332  			}
   333  			ret, err := node.process(&event, ctx, map[string]interface{}{"evt": &event})
   334  			if err != nil {
   335  				clog.Errorf("Error while processing node : %v", err)
   336  				return event, err
   337  			}
   338  			clog.Tracef("node (%s) ret : %v", node.rn, ret)
   339  			if ParseDump {
   340  				var parserIdxInStage int
   341  				StageParseMutex.Lock()
   342  				if len(StageParseCache[stage][node.Name]) == 0 {
   343  					StageParseCache[stage][node.Name] = make([]dumps.ParserResult, 0)
   344  					parserIdxInStage = len(StageParseCache[stage])
   345  				} else {
   346  					parserIdxInStage = StageParseCache[stage][node.Name][0].Idx
   347  				}
   348  				StageParseMutex.Unlock()
   349  
   350  				evtcopy := deepcopy.Copy(event)
   351  				parserInfo := dumps.ParserResult{Evt: evtcopy.(types.Event), Success: ret, Idx: parserIdxInStage}
   352  				StageParseMutex.Lock()
   353  				StageParseCache[stage][node.Name] = append(StageParseCache[stage][node.Name], parserInfo)
   354  				StageParseMutex.Unlock()
   355  			}
   356  			if ret {
   357  				isStageOK = true
   358  			}
   359  			if ret && node.OnSuccess == "next_stage" {
   360  				clog.Debugf("node successful, stop end stage %s", stage)
   361  				break
   362  			}
   363  			//the parsed object moved onto the next phase
   364  			if event.Stage != stage {
   365  				clog.Tracef("node moved stage, break and redo")
   366  				break
   367  			}
   368  		}
   369  		if !isStageOK {
   370  			log.Debugf("Log didn't finish stage %s", event.Stage)
   371  			event.Process = false
   372  			return event, nil
   373  		}
   374  	}
   375  
   376  	event.Process = true
   377  	return event, nil
   378  }