github.com/crowdsecurity/crowdsec@v1.6.1/pkg/parser/node.go (about) 1 package parser 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/antonmedv/expr" 10 "github.com/antonmedv/expr/vm" 11 "github.com/davecgh/go-spew/spew" 12 "github.com/prometheus/client_golang/prometheus" 13 log "github.com/sirupsen/logrus" 14 yaml "gopkg.in/yaml.v2" 15 16 "github.com/crowdsecurity/grokky" 17 18 "github.com/crowdsecurity/crowdsec/pkg/cache" 19 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 20 "github.com/crowdsecurity/crowdsec/pkg/types" 21 ) 22 23 type Node struct { 24 FormatVersion string `yaml:"format"` 25 //Enable config + runtime debug of node via config o/ 26 Debug bool `yaml:"debug,omitempty"` 27 //If enabled, the node (and its child) will report their own statistics 28 Profiling bool `yaml:"profiling,omitempty"` 29 //Name, author, description and reference(s) for parser pattern 30 Name string `yaml:"name,omitempty"` 31 Author string `yaml:"author,omitempty"` 32 Description string `yaml:"description,omitempty"` 33 References []string `yaml:"references,omitempty"` 34 //if debug is present in the node, keep its specific Logger in runtime structure 35 Logger *log.Entry `yaml:"-"` 36 //This is mostly a hack to make writing less repetitive. 37 //relying on stage, we know which field to parse, and we 38 //can also promote log to next stage on success 39 Stage string `yaml:"stage,omitempty"` 40 //OnSuccess allows to tag a node to be able to move log to next stage on success 41 OnSuccess string `yaml:"onsuccess,omitempty"` 42 rn string //this is only for us in debug, a random generated name for each node 43 //Filter is executed at runtime (with current log line as context) 44 //and must succeed or node is exited 45 Filter string `yaml:"filter,omitempty"` 46 RunTimeFilter *vm.Program `yaml:"-" json:"-"` //the actual compiled filter 47 //If node has leafs, execute all of them until one asks for a 'break' 48 LeavesNodes []Node `yaml:"nodes,omitempty"` 49 //Flag used to describe when to 'break' or return an 'error' 50 EnrichFunctions EnricherCtx 51 52 /* If the node is actually a leaf, it can have : grok, enrich, statics */ 53 //pattern_syntax are named grok patterns that are re-utilized over several grok patterns 54 SubGroks yaml.MapSlice `yaml:"pattern_syntax,omitempty"` 55 56 //Holds a grok pattern 57 Grok GrokPattern `yaml:"grok,omitempty"` 58 //Statics can be present in any type of node and is executed last 59 Statics []ExtraField `yaml:"statics,omitempty"` 60 //Stash allows to capture data from the log line and store it in an accessible cache 61 Stash []DataCapture `yaml:"stash,omitempty"` 62 //Whitelists 63 Whitelist Whitelist `yaml:"whitelist,omitempty"` 64 Data []*types.DataSource `yaml:"data,omitempty"` 65 } 66 67 func (n *Node) validate(pctx *UnixParserCtx, ectx EnricherCtx) error { 68 69 //stage is being set automagically 70 if n.Stage == "" { 71 return fmt.Errorf("stage needs to be an existing stage") 72 } 73 74 /* "" behaves like continue */ 75 if n.OnSuccess != "continue" && n.OnSuccess != "next_stage" && n.OnSuccess != "" { 76 return fmt.Errorf("onsuccess '%s' not continue,next_stage", n.OnSuccess) 77 } 78 if n.Filter != "" && n.RunTimeFilter == nil { 79 return fmt.Errorf("non-empty filter '%s' was not compiled", n.Filter) 80 } 81 82 if n.Grok.RunTimeRegexp != nil || n.Grok.TargetField != "" { 83 if n.Grok.TargetField == "" && n.Grok.ExpValue == "" { 84 return fmt.Errorf("grok requires 'expression' or 'apply_on'") 85 } 86 if n.Grok.RegexpName == "" && n.Grok.RegexpValue == "" { 87 return fmt.Errorf("grok needs 'pattern' or 'name'") 88 } 89 } 90 91 for idx, static := range n.Statics { 92 if static.Method != "" { 93 if static.ExpValue == "" { 94 return fmt.Errorf("static %d : when method is set, expression must be present", idx) 95 } 96 if _, ok := ectx.Registered[static.Method]; !ok { 97 log.Warningf("the method '%s' doesn't exist or the plugin has not been initialized", static.Method) 98 } 99 } else { 100 if static.Meta == "" && static.Parsed == "" && static.TargetByName == "" { 101 return fmt.Errorf("static %d : at least one of meta/event/target must be set", idx) 102 } 103 if static.Value == "" && static.RunTimeValue == nil { 104 return fmt.Errorf("static %d value or expression must be set", idx) 105 } 106 } 107 } 108 109 for idx, stash := range n.Stash { 110 if stash.Name == "" { 111 return fmt.Errorf("stash %d : name must be set", idx) 112 } 113 if stash.Value == "" { 114 return fmt.Errorf("stash %s : value expression must be set", stash.Name) 115 } 116 if stash.Key == "" { 117 return fmt.Errorf("stash %s : key expression must be set", stash.Name) 118 } 119 if stash.TTL == "" { 120 return fmt.Errorf("stash %s : ttl must be set", stash.Name) 121 } 122 if stash.Strategy == "" { 123 stash.Strategy = "LRU" 124 } 125 //should be configurable 126 if stash.MaxMapSize == 0 { 127 stash.MaxMapSize = 100 128 } 129 } 130 return nil 131 } 132 133 func (n *Node) process(p *types.Event, ctx UnixParserCtx, expressionEnv map[string]interface{}) (bool, error) { 134 var NodeState bool 135 var NodeHasOKGrok bool 136 clog := n.Logger 137 138 cachedExprEnv := expressionEnv 139 140 clog.Tracef("Event entering node") 141 if n.RunTimeFilter != nil { 142 //Evaluate node's filter 143 output, err := exprhelpers.Run(n.RunTimeFilter, cachedExprEnv, clog, n.Debug) 144 if err != nil { 145 clog.Warningf("failed to run filter : %v", err) 146 clog.Debugf("Event leaving node : ko") 147 return false, nil 148 } 149 150 switch out := output.(type) { 151 case bool: 152 if !out { 153 clog.Debugf("Event leaving node : ko (failed filter)") 154 return false, nil 155 } 156 default: 157 clog.Warningf("Expr '%s' returned non-bool, abort : %T", n.Filter, output) 158 clog.Debugf("Event leaving node : ko") 159 return false, nil 160 } 161 NodeState = true 162 } else { 163 clog.Tracef("Node has not filter, enter") 164 NodeState = true 165 } 166 167 if n.Name != "" { 168 NodesHits.With(prometheus.Labels{"source": p.Line.Src, "type": p.Line.Module, "name": n.Name}).Inc() 169 } 170 exprErr := error(nil) 171 isWhitelisted := n.CheckIPsWL(p) 172 if !isWhitelisted { 173 isWhitelisted, exprErr = n.CheckExprWL(cachedExprEnv, p) 174 } 175 if exprErr != nil { 176 // Previous code returned nil if there was an error, so we keep this behavior 177 return false, nil //nolint:nilerr 178 } 179 if isWhitelisted && !p.Whitelisted { 180 p.Whitelisted = true 181 p.WhitelistReason = n.Whitelist.Reason 182 /*huglily wipe the ban order if the event is whitelisted and it's an overflow */ 183 if p.Type == types.OVFLW { /*don't do this at home kids */ 184 ips := []string{} 185 for k := range p.Overflow.Sources { 186 ips = append(ips, k) 187 } 188 clog.Infof("Ban for %s whitelisted, reason [%s]", strings.Join(ips, ","), n.Whitelist.Reason) 189 p.Overflow.Whitelisted = true 190 } 191 } 192 193 //Process grok if present, should be exclusive with nodes :) 194 gstr := "" 195 if n.Grok.RunTimeRegexp != nil { 196 clog.Tracef("Processing grok pattern : %s : %p", n.Grok.RegexpName, n.Grok.RunTimeRegexp) 197 //for unparsed, parsed etc. set sensible defaults to reduce user hassle 198 if n.Grok.TargetField != "" { 199 //it's a hack to avoid using real reflect 200 if n.Grok.TargetField == "Line.Raw" { 201 gstr = p.Line.Raw 202 } else if val, ok := p.Parsed[n.Grok.TargetField]; ok { 203 gstr = val 204 } else { 205 clog.Debugf("(%s) target field '%s' doesn't exist in %v", n.rn, n.Grok.TargetField, p.Parsed) 206 NodeState = false 207 } 208 } else if n.Grok.RunTimeValue != nil { 209 output, err := exprhelpers.Run(n.Grok.RunTimeValue, cachedExprEnv, clog, n.Debug) 210 if err != nil { 211 clog.Warningf("failed to run RunTimeValue : %v", err) 212 NodeState = false 213 } 214 switch out := output.(type) { 215 case string: 216 gstr = out 217 case int: 218 gstr = fmt.Sprintf("%d", out) 219 case float64, float32: 220 gstr = fmt.Sprintf("%f", out) 221 default: 222 clog.Errorf("unexpected return type for RunTimeValue : %T", output) 223 } 224 } 225 226 var groklabel string 227 if n.Grok.RegexpName == "" { 228 groklabel = fmt.Sprintf("%5.5s...", n.Grok.RegexpValue) 229 } else { 230 groklabel = n.Grok.RegexpName 231 } 232 grok := n.Grok.RunTimeRegexp.Parse(gstr) 233 if len(grok) > 0 { 234 /*tag explicitly that the *current* node had a successful grok pattern. it's important to know success state*/ 235 NodeHasOKGrok = true 236 clog.Debugf("+ Grok '%s' returned %d entries to merge in Parsed", groklabel, len(grok)) 237 //We managed to grok stuff, merged into parse 238 for k, v := range grok { 239 clog.Debugf("\t.Parsed['%s'] = '%s'", k, v) 240 p.Parsed[k] = v 241 } 242 // if the grok succeed, process associated statics 243 err := n.ProcessStatics(n.Grok.Statics, p) 244 if err != nil { 245 clog.Errorf("(%s) Failed to process statics : %v", n.rn, err) 246 return false, err 247 } 248 } else { 249 //grok failed, node failed 250 clog.Debugf("+ Grok '%s' didn't return data on '%s'", groklabel, gstr) 251 NodeState = false 252 } 253 254 } else { 255 clog.Tracef("! No grok pattern : %p", n.Grok.RunTimeRegexp) 256 } 257 258 //Process the stash (data collection) if : a grok was present and succeeded, or if there is no grok 259 if NodeHasOKGrok || n.Grok.RunTimeRegexp == nil { 260 for idx, stash := range n.Stash { 261 var value string 262 var key string 263 if stash.ValueExpression == nil { 264 clog.Warningf("Stash %d has no value expression, skipping", idx) 265 continue 266 } 267 if stash.KeyExpression == nil { 268 clog.Warningf("Stash %d has no key expression, skipping", idx) 269 continue 270 } 271 //collect the data 272 output, err := exprhelpers.Run(stash.ValueExpression, cachedExprEnv, clog, n.Debug) 273 if err != nil { 274 clog.Warningf("Error while running stash val expression : %v", err) 275 } 276 //can we expect anything else than a string ? 277 switch output := output.(type) { 278 case string: 279 value = output 280 default: 281 clog.Warningf("unexpected type %t (%v) while running '%s'", output, output, stash.Value) 282 continue 283 } 284 285 //collect the key 286 output, err = exprhelpers.Run(stash.KeyExpression, cachedExprEnv, clog, n.Debug) 287 if err != nil { 288 clog.Warningf("Error while running stash key expression : %v", err) 289 } 290 //can we expect anything else than a string ? 291 switch output := output.(type) { 292 case string: 293 key = output 294 default: 295 clog.Warningf("unexpected type %t (%v) while running '%s'", output, output, stash.Key) 296 continue 297 } 298 cache.SetKey(stash.Name, key, value, &stash.TTLVal) 299 } 300 } 301 302 //Iterate on leafs 303 for _, leaf := range n.LeavesNodes { 304 ret, err := leaf.process(p, ctx, cachedExprEnv) 305 if err != nil { 306 clog.Tracef("\tNode (%s) failed : %v", leaf.rn, err) 307 clog.Debugf("Event leaving node : ko") 308 return false, err 309 } 310 clog.Tracef("\tsub-node (%s) ret : %v (strategy:%s)", leaf.rn, ret, n.OnSuccess) 311 if ret { 312 NodeState = true 313 /* if child is successful, stop processing */ 314 if n.OnSuccess == "next_stage" { 315 clog.Debugf("child is success, OnSuccess=next_stage, skip") 316 break 317 } 318 } else if !NodeHasOKGrok { 319 /* 320 If the parent node has a successful grok pattern, it's state will stay successful even if one or more chil fails. 321 If the parent node is a skeleton node (no grok pattern), then at least one child must be successful for it to be a success. 322 */ 323 NodeState = false 324 } 325 } 326 /*todo : check if a node made the state change ?*/ 327 /* should the childs inherit the on_success behavior */ 328 329 clog.Tracef("State after nodes : %v", NodeState) 330 331 //grok or leafs failed, don't process statics 332 if !NodeState { 333 if n.Name != "" { 334 NodesHitsKo.With(prometheus.Labels{"source": p.Line.Src, "type": p.Line.Module, "name": n.Name}).Inc() 335 } 336 clog.Debugf("Event leaving node : ko") 337 return NodeState, nil 338 } 339 340 if n.Name != "" { 341 NodesHitsOk.With(prometheus.Labels{"source": p.Line.Src, "type": p.Line.Module, "name": n.Name}).Inc() 342 } 343 344 /* 345 This is to apply statics when the node either was whitelisted, or is not a whitelist (it has no expr/ips wl) 346 It is overconvoluted and should be simplified 347 */ 348 if len(n.Statics) > 0 && (isWhitelisted || !n.ContainsWLs()) { 349 clog.Debugf("+ Processing %d statics", len(n.Statics)) 350 // if all else is good in whitelist, process node's statics 351 err := n.ProcessStatics(n.Statics, p) 352 if err != nil { 353 clog.Errorf("Failed to process statics : %v", err) 354 return false, err 355 } 356 } else { 357 clog.Tracef("! No node statics") 358 } 359 360 if NodeState { 361 clog.Debugf("Event leaving node : ok") 362 log.Tracef("node is successful, check strategy") 363 if n.OnSuccess == "next_stage" { 364 idx := stageidx(p.Stage, ctx.Stages) 365 //we're at the last stage 366 if idx+1 == len(ctx.Stages) { 367 clog.Debugf("node reached the last stage : %s", p.Stage) 368 } else { 369 clog.Debugf("move Event from stage %s to %s", p.Stage, ctx.Stages[idx+1]) 370 p.Stage = ctx.Stages[idx+1] 371 } 372 } else { 373 clog.Tracef("no strategy on success (%s), continue !", n.OnSuccess) 374 } 375 } else { 376 clog.Debugf("Event leaving node : ko") 377 } 378 clog.Tracef("Node successful, continue") 379 return NodeState, nil 380 } 381 382 func (n *Node) compile(pctx *UnixParserCtx, ectx EnricherCtx) error { 383 var err error 384 var valid bool 385 386 valid = false 387 388 dumpr := spew.ConfigState{MaxDepth: 1, DisablePointerAddresses: true} 389 n.rn = seed.Generate() 390 391 n.EnrichFunctions = ectx 392 log.Tracef("compile, node is %s", n.Stage) 393 /* if the node has debugging enabled, create a specific logger with debug 394 that will be used only for processing this node ;) */ 395 if n.Debug { 396 var clog = log.New() 397 if err = types.ConfigureLogger(clog); err != nil { 398 log.Fatalf("While creating bucket-specific logger : %s", err) 399 } 400 clog.SetLevel(log.DebugLevel) 401 n.Logger = clog.WithFields(log.Fields{ 402 "id": n.rn, 403 }) 404 n.Logger.Infof("%s has debug enabled", n.Name) 405 } else { 406 /* else bind it to the default one (might find something more elegant here)*/ 407 n.Logger = log.WithFields(log.Fields{ 408 "id": n.rn, 409 }) 410 } 411 412 /* display info about top-level nodes, they should be the only one with explicit stage name ?*/ 413 n.Logger = n.Logger.WithFields(log.Fields{"stage": n.Stage, "name": n.Name}) 414 415 n.Logger.Tracef("Compiling : %s", dumpr.Sdump(n)) 416 417 //compile filter if present 418 if n.Filter != "" { 419 n.RunTimeFilter, err = expr.Compile(n.Filter, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 420 if err != nil { 421 return fmt.Errorf("compilation of '%s' failed: %v", n.Filter, err) 422 } 423 } 424 425 /* handle pattern_syntax and groks */ 426 for _, pattern := range n.SubGroks { 427 n.Logger.Tracef("Adding subpattern '%s' : '%s'", pattern.Key, pattern.Value) 428 if err = pctx.Grok.Add(pattern.Key.(string), pattern.Value.(string)); err != nil { 429 if errors.Is(err, grokky.ErrAlreadyExist) { 430 n.Logger.Warningf("grok '%s' already registred", pattern.Key) 431 continue 432 } 433 n.Logger.Errorf("Unable to compile subpattern %s : %v", pattern.Key, err) 434 return err 435 } 436 } 437 438 /* load grok by name or compile in-place */ 439 if n.Grok.RegexpName != "" { 440 n.Logger.Tracef("+ Regexp Compilation '%s'", n.Grok.RegexpName) 441 n.Grok.RunTimeRegexp, err = pctx.Grok.Get(n.Grok.RegexpName) 442 if err != nil { 443 return fmt.Errorf("unable to find grok '%s' : %v", n.Grok.RegexpName, err) 444 } 445 if n.Grok.RunTimeRegexp == nil { 446 return fmt.Errorf("empty grok '%s'", n.Grok.RegexpName) 447 } 448 n.Logger.Tracef("%s regexp: %s", n.Grok.RegexpName, n.Grok.RunTimeRegexp.String()) 449 valid = true 450 } else if n.Grok.RegexpValue != "" { 451 if strings.HasSuffix(n.Grok.RegexpValue, "\n") { 452 n.Logger.Debugf("Beware, pattern ends with \\n : '%s'", n.Grok.RegexpValue) 453 } 454 n.Grok.RunTimeRegexp, err = pctx.Grok.Compile(n.Grok.RegexpValue) 455 if err != nil { 456 return fmt.Errorf("failed to compile grok '%s': %v", n.Grok.RegexpValue, err) 457 } 458 if n.Grok.RunTimeRegexp == nil { 459 // We shouldn't be here because compilation succeeded, so regexp shouldn't be nil 460 return fmt.Errorf("grok compilation failure: %s", n.Grok.RegexpValue) 461 } 462 n.Logger.Tracef("%s regexp : %s", n.Grok.RegexpValue, n.Grok.RunTimeRegexp.String()) 463 valid = true 464 } 465 466 /*if grok source is an expression*/ 467 if n.Grok.ExpValue != "" { 468 n.Grok.RunTimeValue, err = expr.Compile(n.Grok.ExpValue, 469 exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 470 if err != nil { 471 return fmt.Errorf("while compiling grok's expression: %w", err) 472 } 473 } 474 475 /* load grok statics */ 476 //compile expr statics if present 477 for idx := range n.Grok.Statics { 478 if n.Grok.Statics[idx].ExpValue != "" { 479 n.Grok.Statics[idx].RunTimeValue, err = expr.Compile(n.Grok.Statics[idx].ExpValue, 480 exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 481 if err != nil { 482 return err 483 } 484 } 485 valid = true 486 } 487 488 /* load data capture (stash) */ 489 for i, stash := range n.Stash { 490 n.Stash[i].ValueExpression, err = expr.Compile(stash.Value, 491 exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 492 if err != nil { 493 return fmt.Errorf("while compiling stash value expression: %w", err) 494 } 495 496 n.Stash[i].KeyExpression, err = expr.Compile(stash.Key, 497 exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 498 if err != nil { 499 return fmt.Errorf("while compiling stash key expression: %w", err) 500 } 501 502 n.Stash[i].TTLVal, err = time.ParseDuration(stash.TTL) 503 if err != nil { 504 return fmt.Errorf("while parsing stash ttl: %w", err) 505 } 506 507 logLvl := n.Logger.Logger.GetLevel() 508 //init the cache, does it make sense to create it here just to be sure everything is fine ? 509 if err = cache.CacheInit(cache.CacheCfg{ 510 Size: n.Stash[i].MaxMapSize, 511 TTL: n.Stash[i].TTLVal, 512 Name: n.Stash[i].Name, 513 Strategy: n.Stash[i].Strategy, 514 LogLevel: &logLvl, 515 }); err != nil { 516 return fmt.Errorf("while initializing cache: %w", err) 517 } 518 } 519 520 /* compile leafs if present */ 521 for idx := range n.LeavesNodes { 522 if n.LeavesNodes[idx].Name == "" { 523 n.LeavesNodes[idx].Name = fmt.Sprintf("child-%s", n.Name) 524 } 525 /*propagate debug/stats to child nodes*/ 526 if !n.LeavesNodes[idx].Debug && n.Debug { 527 n.LeavesNodes[idx].Debug = true 528 } 529 if !n.LeavesNodes[idx].Profiling && n.Profiling { 530 n.LeavesNodes[idx].Profiling = true 531 } 532 n.LeavesNodes[idx].Stage = n.Stage 533 err = n.LeavesNodes[idx].compile(pctx, ectx) 534 if err != nil { 535 return err 536 } 537 valid = true 538 } 539 540 /* load statics if present */ 541 for idx := range n.Statics { 542 if n.Statics[idx].ExpValue != "" { 543 n.Statics[idx].RunTimeValue, err = expr.Compile(n.Statics[idx].ExpValue, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...) 544 if err != nil { 545 n.Logger.Errorf("Statics Compilation failed %v.", err) 546 return err 547 } 548 } 549 valid = true 550 } 551 552 /* compile whitelists if present */ 553 whitelistValid, err := n.CompileWLs() 554 if err != nil { 555 return err 556 } 557 valid = valid || whitelistValid 558 559 if !valid { 560 /* node is empty, error force return */ 561 n.Logger.Error("Node is empty or invalid, abort") 562 n.Stage = "" 563 return fmt.Errorf("Node is empty") 564 } 565 566 if err := n.validate(pctx, ectx); err != nil { 567 return err 568 } 569 570 return nil 571 }