github.com/crowdsecurity/crowdsec@v1.6.1/pkg/leakybucket/bayesian.go (about) 1 package leakybucket 2 3 import ( 4 "fmt" 5 6 "github.com/antonmedv/expr" 7 "github.com/antonmedv/expr/vm" 8 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 9 "github.com/crowdsecurity/crowdsec/pkg/types" 10 ) 11 12 type RawBayesianCondition struct { 13 ConditionalFilterName string `yaml:"condition"` 14 ProbGivenEvil float32 `yaml:"prob_given_evil"` 15 ProbGivenBenign float32 `yaml:"prob_given_benign"` 16 Guillotine bool `yaml:"guillotine,omitempty"` 17 } 18 19 type BayesianEvent struct { 20 rawCondition RawBayesianCondition 21 conditionalFilterRuntime *vm.Program 22 guillotineState bool 23 } 24 25 type BayesianBucket struct { 26 bayesianEventArray []*BayesianEvent 27 prior float32 28 threshold float32 29 posterior float32 30 DumbProcessor 31 } 32 33 func updateProbability(prior, probGivenEvil, ProbGivenBenign float32) float32 { 34 numerator := probGivenEvil * prior 35 denominator := numerator + ProbGivenBenign*(1-prior) 36 37 return numerator / denominator 38 } 39 40 func (c *BayesianBucket) OnBucketInit(g *BucketFactory) error { 41 var err error 42 BayesianEventArray := make([]*BayesianEvent, len(g.BayesianConditions)) 43 44 if conditionalExprCache == nil { 45 conditionalExprCache = make(map[string]vm.Program) 46 } 47 conditionalExprCacheLock.Lock() 48 49 for index, bcond := range g.BayesianConditions { 50 var bayesianEvent BayesianEvent 51 bayesianEvent.rawCondition = bcond 52 err = bayesianEvent.compileCondition() 53 if err != nil { 54 return err 55 } 56 BayesianEventArray[index] = &bayesianEvent 57 } 58 conditionalExprCacheLock.Unlock() 59 c.bayesianEventArray = BayesianEventArray 60 61 c.prior = g.BayesianPrior 62 c.threshold = g.BayesianThreshold 63 64 return err 65 } 66 67 func (c *BayesianBucket) AfterBucketPour(b *BucketFactory) func(types.Event, *Leaky) *types.Event { 68 return func(msg types.Event, l *Leaky) *types.Event { 69 c.posterior = c.prior 70 l.logger.Debugf("starting bayesian evaluation with prior: %v", c.posterior) 71 72 for _, bevent := range c.bayesianEventArray { 73 err := bevent.bayesianUpdate(c, msg, l) 74 if err != nil { 75 l.logger.Errorf("bayesian update failed for %s with %s", bevent.rawCondition.ConditionalFilterName, err) 76 } 77 } 78 79 l.logger.Debugf("value of posterior after events : %v", c.posterior) 80 81 if c.posterior > c.threshold { 82 l.logger.Debugf("Bayesian bucket overflow") 83 l.Ovflw_ts = l.Last_ts 84 l.Out <- l.Queue 85 return nil 86 } 87 88 return &msg 89 } 90 } 91 92 func (b *BayesianEvent) bayesianUpdate(c *BayesianBucket, msg types.Event, l *Leaky) error { 93 var condition, ok bool 94 95 if b.conditionalFilterRuntime == nil { 96 l.logger.Tracef("empty conditional filter runtime for %s", b.rawCondition.ConditionalFilterName) 97 return nil 98 } 99 100 l.logger.Tracef("guillotine value for %s : %v", b.rawCondition.ConditionalFilterName, b.getGuillotineState()) 101 if b.getGuillotineState() { 102 l.logger.Tracef("guillotine already triggered for %s", b.rawCondition.ConditionalFilterName) 103 l.logger.Tracef("condition true updating prior for: %s", b.rawCondition.ConditionalFilterName) 104 c.posterior = updateProbability(c.posterior, b.rawCondition.ProbGivenEvil, b.rawCondition.ProbGivenBenign) 105 l.logger.Tracef("new value of posterior : %v", c.posterior) 106 return nil 107 } 108 109 l.logger.Debugf("running condition expression: %s", b.rawCondition.ConditionalFilterName) 110 ret, err := exprhelpers.Run(b.conditionalFilterRuntime, map[string]interface{}{"evt": &msg, "queue": l.Queue, "leaky": l}, l.logger, l.BucketConfig.Debug) 111 if err != nil { 112 return fmt.Errorf("unable to run conditional filter: %s", err) 113 } 114 115 l.logger.Tracef("bayesian bucket expression %s returned : %v", b.rawCondition.ConditionalFilterName, ret) 116 if condition, ok = ret.(bool); !ok { 117 return fmt.Errorf("bayesian condition unexpected non-bool return: %T", ret) 118 } 119 120 l.logger.Tracef("condition %T updating prior for: %s", condition, b.rawCondition.ConditionalFilterName) 121 if condition { 122 c.posterior = updateProbability(c.posterior, b.rawCondition.ProbGivenEvil, b.rawCondition.ProbGivenBenign) 123 b.triggerGuillotine() 124 } else { 125 c.posterior = updateProbability(c.posterior, 1-b.rawCondition.ProbGivenEvil, 1-b.rawCondition.ProbGivenBenign) 126 } 127 l.logger.Tracef("new value of posterior: %v", c.posterior) 128 129 return nil 130 } 131 132 func (b *BayesianEvent) getGuillotineState() bool { 133 if b.rawCondition.Guillotine { 134 return b.guillotineState 135 } 136 return false 137 } 138 139 func (b *BayesianEvent) triggerGuillotine() { 140 b.guillotineState = true 141 } 142 143 func (b *BayesianEvent) compileCondition() error { 144 var err error 145 var compiledExpr *vm.Program 146 147 if compiled, ok := conditionalExprCache[b.rawCondition.ConditionalFilterName]; ok { 148 b.conditionalFilterRuntime = &compiled 149 return nil 150 } 151 152 conditionalExprCacheLock.Unlock() 153 //release the lock during compile same as coditional bucket 154 compiledExpr, err = expr.Compile(b.rawCondition.ConditionalFilterName, exprhelpers.GetExprOptions(map[string]interface{}{"queue": &types.Queue{}, "leaky": &Leaky{}, "evt": &types.Event{}})...) 155 if err != nil { 156 return fmt.Errorf("bayesian condition compile error: %w", err) 157 } 158 b.conditionalFilterRuntime = compiledExpr 159 conditionalExprCacheLock.Lock() 160 conditionalExprCache[b.rawCondition.ConditionalFilterName] = *compiledExpr 161 162 return nil 163 }