github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/compiler/eval/rule.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the Apache License Version 2.0.
     3  // This product includes software developed at Datadog (https://www.datadoghq.com/).
     4  // Copyright 2016-present Datadog, Inc.
     5  
     6  // Package eval holds eval related files
     7  package eval
     8  
     9  import (
    10  	"fmt"
    11  	"reflect"
    12  	"slices"
    13  
    14  	"github.com/DataDog/datadog-agent/pkg/security/secl/compiler/ast"
    15  	"github.com/DataDog/datadog-agent/pkg/security/secl/utils"
    16  )
    17  
    18  // RuleID - ID of a Rule
    19  type RuleID = string
    20  
    21  // RuleSetTagValue - Value of the "ruleset" tag
    22  type RuleSetTagValue = string
    23  
    24  // Rule - Rule object identified by an `ID` containing a SECL `Expression`
    25  type Rule struct {
    26  	ID          RuleID
    27  	Expression  string
    28  	Tags        []string
    29  	Model       Model
    30  	Opts        *Opts
    31  	pprofLabels utils.LabelSet
    32  
    33  	evaluator *RuleEvaluator
    34  	ast       *ast.Rule
    35  }
    36  
    37  // RuleEvaluator - Evaluation part of a Rule
    38  type RuleEvaluator struct {
    39  	Eval       BoolEvalFnc
    40  	EventTypes []EventType
    41  
    42  	fieldValues map[Field][]FieldValue
    43  	fields      []Field
    44  
    45  	partialEvals map[Field]BoolEvalFnc
    46  }
    47  
    48  // NewRule returns a new rule
    49  func NewRule(id string, expression string, opts *Opts, tags ...string) *Rule {
    50  	if opts.MacroStore == nil {
    51  		opts.WithMacroStore(&MacroStore{})
    52  	}
    53  	if opts.VariableStore == nil {
    54  		opts.WithVariableStore(&VariableStore{})
    55  	}
    56  
    57  	labelSet, err := utils.NewLabelSet("rule_id", id)
    58  	if err != nil {
    59  		panic(err)
    60  	}
    61  
    62  	return &Rule{
    63  		ID:          id,
    64  		Expression:  expression,
    65  		Opts:        opts,
    66  		Tags:        tags,
    67  		pprofLabels: labelSet,
    68  	}
    69  }
    70  
    71  // IsPartialAvailable checks if partial have been generated for the given Field
    72  func (r *RuleEvaluator) IsPartialAvailable(field Field) bool {
    73  	_, exists := r.partialEvals[field]
    74  	return exists
    75  }
    76  
    77  // PartialEval partially evaluation of the Rule with the given Field.
    78  func (r *RuleEvaluator) PartialEval(ctx *Context, field Field) (bool, error) {
    79  	eval, ok := r.partialEvals[field]
    80  	if !ok {
    81  		return false, &ErrFieldNotFound{Field: field}
    82  	}
    83  
    84  	return eval(ctx), nil
    85  }
    86  
    87  func (r *RuleEvaluator) setPartial(field string, partialEval BoolEvalFnc) {
    88  	if r.partialEvals == nil {
    89  		r.partialEvals = make(map[string]BoolEvalFnc)
    90  	}
    91  	r.partialEvals[field] = partialEval
    92  }
    93  
    94  // GetFields - Returns all the Field that the RuleEvaluator handles
    95  func (r *RuleEvaluator) GetFields() []Field {
    96  	return r.fields
    97  }
    98  
    99  // Eval - Evaluates
   100  func (r *Rule) Eval(ctx *Context) bool {
   101  	return r.evaluator.Eval(ctx)
   102  }
   103  
   104  // GetFieldValues returns the values of the given field
   105  func (r *Rule) GetFieldValues(field Field) []FieldValue {
   106  	return r.evaluator.fieldValues[field]
   107  }
   108  
   109  // PartialEval - Partial evaluation with the given Field
   110  func (r *Rule) PartialEval(ctx *Context, field Field) (bool, error) {
   111  	if !r.evaluator.IsPartialAvailable(field) {
   112  		if err := r.genPartials(field); err != nil {
   113  			return false, err
   114  		}
   115  	}
   116  
   117  	return r.evaluator.PartialEval(ctx, field)
   118  }
   119  
   120  // GetPartialEval - Returns the Partial RuleEvaluator for the given Field
   121  func (r *Rule) GetPartialEval(field Field) BoolEvalFnc {
   122  	partial, exists := r.evaluator.partialEvals[field]
   123  	if !exists {
   124  		if err := r.genPartials(field); err != nil {
   125  			return nil
   126  		}
   127  		partial = r.evaluator.partialEvals[field]
   128  	}
   129  
   130  	return partial
   131  }
   132  
   133  // GetFields - Returns all the Field of the Rule including field of the Macro used
   134  func (r *Rule) GetFields() []Field {
   135  	fields := r.evaluator.GetFields()
   136  
   137  	for _, macro := range r.Opts.MacroStore.List() {
   138  		fields = append(fields, macro.GetFields()...)
   139  	}
   140  
   141  	return fields
   142  }
   143  
   144  // GetPprofLabels returns the pprof labels
   145  func (r *Rule) GetPprofLabels() utils.LabelSet {
   146  	return r.pprofLabels
   147  }
   148  
   149  // GetEvaluator - Returns the RuleEvaluator of the Rule corresponding to the SECL `Expression`
   150  func (r *Rule) GetEvaluator() *RuleEvaluator {
   151  	return r.evaluator
   152  }
   153  
   154  // GetEventTypes - Returns a list of all the event that the `Expression` handles
   155  func (r *Rule) GetEventTypes() ([]EventType, error) {
   156  	if r.evaluator == nil {
   157  		return nil, &ErrRuleNotCompiled{RuleID: r.ID}
   158  	}
   159  
   160  	eventTypes := r.evaluator.EventTypes
   161  
   162  	for _, macro := range r.Opts.MacroStore.List() {
   163  		eventTypes = append(eventTypes, macro.GetEventTypes()...)
   164  	}
   165  
   166  	return eventTypes, nil
   167  }
   168  
   169  // GetAst - Returns the representation of the SECL `Expression`
   170  func (r *Rule) GetAst() *ast.Rule {
   171  	return r.ast
   172  }
   173  
   174  // Parse - Transforms the SECL `Expression` into its AST representation
   175  func (r *Rule) Parse(parsingContext *ast.ParsingContext) error {
   176  	astRule, err := parsingContext.ParseRule(r.Expression)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	r.ast = astRule
   181  	return nil
   182  }
   183  
   184  // NewRuleEvaluator returns a new evaluator for a rule
   185  func NewRuleEvaluator(rule *ast.Rule, model Model, opts *Opts) (*RuleEvaluator, error) {
   186  	macros := make(map[MacroID]*MacroEvaluator)
   187  	for _, macro := range opts.MacroStore.List() {
   188  		macros[macro.ID] = macro.evaluator
   189  	}
   190  	state := NewState(model, "", macros)
   191  
   192  	eval, _, err := nodeToEvaluator(rule.BooleanExpression, opts, state)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	evalBool, ok := eval.(*BoolEvaluator)
   198  	if !ok {
   199  		return nil, NewTypeError(rule.Pos, reflect.Bool)
   200  	}
   201  
   202  	events, err := eventTypesFromFields(model, state)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	// direct value, no bool evaluator, wrap value
   208  	if evalBool.EvalFnc == nil {
   209  		evalBool.EvalFnc = func(ctx *Context) bool {
   210  			return evalBool.Value
   211  		}
   212  	}
   213  
   214  	return &RuleEvaluator{
   215  		Eval:        evalBool.EvalFnc,
   216  		EventTypes:  events,
   217  		fieldValues: state.fieldValues,
   218  		fields:      KeysOfMap(state.fieldValues),
   219  	}, nil
   220  }
   221  
   222  // GenEvaluator - Compile and generates the RuleEvaluator
   223  func (r *Rule) GenEvaluator(model Model, parsingCtx *ast.ParsingContext) error {
   224  	r.Model = model
   225  
   226  	if r.ast == nil {
   227  		if err := r.Parse(parsingCtx); err != nil {
   228  			return err
   229  		}
   230  	}
   231  
   232  	evaluator, err := NewRuleEvaluator(r.ast, model, r.Opts)
   233  	if err != nil {
   234  		if err, ok := err.(*ErrAstToEval); ok {
   235  			return fmt.Errorf("rule syntax error: %s: %w", err, &ErrRuleParse{pos: err.Pos, expr: r.Expression})
   236  		}
   237  		return fmt.Errorf("rule compilation error: %w", err)
   238  	}
   239  	r.evaluator = evaluator
   240  
   241  	return nil
   242  }
   243  
   244  func (r *Rule) genMacroPartials(field Field) (map[MacroID]*MacroEvaluator, error) {
   245  	macroEvaluators := make(map[MacroID]*MacroEvaluator)
   246  	for _, macro := range r.Opts.MacroStore.List() {
   247  		var err error
   248  		var evaluator *MacroEvaluator
   249  		if macro.ast != nil {
   250  			// NOTE(safchain) this is not working with nested macro. It will be removed once partial
   251  			// will be generated another way
   252  			evaluator, err = macroToEvaluator(macro.ast, r.Model, r.Opts, field)
   253  			if err != nil {
   254  				if err, ok := err.(*ErrAstToEval); ok {
   255  					return nil, fmt.Errorf("macro syntax error: %w", &ErrRuleParse{pos: err.Pos})
   256  				}
   257  				return nil, fmt.Errorf("macro compilation error: %w", err)
   258  			}
   259  		} else {
   260  			evaluator = macro.GetEvaluator()
   261  		}
   262  
   263  		macroEvaluators[macro.ID] = evaluator
   264  	}
   265  
   266  	return macroEvaluators, nil
   267  }
   268  
   269  // GenPartials - Compiles and generates partial Evaluators
   270  func (r *Rule) genPartials(field Field) error {
   271  	if !slices.Contains(r.GetFields(), field) {
   272  		return nil
   273  	}
   274  
   275  	macroPartial, err := r.genMacroPartials(field)
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	state := NewState(r.Model, field, macroPartial)
   281  	pEval, _, err := nodeToEvaluator(r.ast.BooleanExpression, r.Opts, state)
   282  	if err != nil {
   283  		return fmt.Errorf("couldn't generate partial for field %s and rule %s: %w", field, r.ID, err)
   284  	}
   285  
   286  	pEvalBool, ok := pEval.(*BoolEvaluator)
   287  	if !ok {
   288  		return NewTypeError(r.ast.Pos, reflect.Bool)
   289  	}
   290  
   291  	if pEvalBool.EvalFnc == nil {
   292  		pEvalBool.EvalFnc = func(ctx *Context) bool {
   293  			return pEvalBool.Value
   294  		}
   295  	}
   296  
   297  	r.evaluator.setPartial(field, pEvalBool.EvalFnc)
   298  
   299  	return nil
   300  }