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