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 }