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  }