github.com/cilium/cilium@v1.16.2/pkg/hubble/filters/cel_expression.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package filters 5 6 import ( 7 "context" 8 "fmt" 9 "reflect" 10 11 "github.com/google/cel-go/cel" 12 "github.com/sirupsen/logrus" 13 14 flowpb "github.com/cilium/cilium/api/v1/flow" 15 v1 "github.com/cilium/cilium/pkg/hubble/api/v1" 16 ) 17 18 var ( 19 // unfortunately, "flow" conflicts with the protobuf package name "flow", so 20 // we have to use something else. 21 // TODO: what should this be? 22 flowVariableName = "_flow" 23 24 celTypes = cel.Types(&flowpb.Flow{}) 25 26 goBoolType = reflect.TypeOf(false) 27 28 celEnv *cel.Env 29 ) 30 31 func init() { 32 var err error 33 celEnv, err = cel.NewEnv( 34 cel.Container("flow"), 35 celTypes, 36 cel.Variable(flowVariableName, cel.ObjectType("flow.Flow")), 37 ) 38 if err != nil { 39 panic(fmt.Sprintf("error creating CEL env %s", err)) 40 } 41 42 } 43 44 // compile will parse and check an expression `expr` against a given 45 // environment `env` and determine whether the resulting type of the expression 46 // matches the `exprType` provided as input. 47 // Copied from 48 // https://github.com/google/cel-go/blob/338b3c80e688f7f44661d163c0dbc02eb120dcb7/codelab/solution/codelab.go#LL385C1-L399C2 49 // with modifications 50 func compile(env *cel.Env, expr string, celType *cel.Type) (*cel.Ast, error) { 51 ast, iss := env.Compile(expr) 52 if iss.Err() != nil { 53 return nil, iss.Err() 54 } 55 // Type-check the expression for correctness. 56 checked, iss := env.Check(ast) 57 // Report semantic errors, if present. 58 if iss.Err() != nil { 59 return nil, iss.Err() 60 } 61 if checked.OutputType() != celType { 62 return nil, fmt.Errorf( 63 "got %q, wanted %q result type", 64 checked.OutputType(), celType) 65 } 66 return ast, nil 67 } 68 69 func filterByCELExpression(ctx context.Context, log logrus.FieldLogger, exprs []string) (FilterFunc, error) { 70 var programs []cel.Program 71 for _, expr := range exprs { 72 // we want filters to be boolean expressions, so check the type of the 73 // expression before proceeding 74 ast, err := compile(celEnv, expr, cel.BoolType) 75 if err != nil { 76 return nil, fmt.Errorf("error compiling CEL expression: %w", err) 77 } 78 79 prg, err := celEnv.Program(ast) 80 if err != nil { 81 return nil, fmt.Errorf("error building CEL program: %w", err) 82 } 83 programs = append(programs, prg) 84 } 85 86 return func(ev *v1.Event) bool { 87 for _, prg := range programs { 88 out, _, err := prg.ContextEval(ctx, map[string]any{ 89 flowVariableName: ev.GetFlow(), 90 }) 91 if err != nil { 92 log.Errorf("error running CEL program %s", err) 93 return false 94 } 95 96 v, err := out.ConvertToNative(goBoolType) 97 if err != nil { 98 log.Errorf("invalid conversion in CEL program: %s", err) 99 return false 100 } 101 b, ok := v.(bool) 102 if ok && b { 103 return true 104 } 105 } 106 return false 107 }, nil 108 } 109 110 // CELExpressionFilter implements filtering based on CEL (common expression 111 // language) expressions 112 type CELExpressionFilter struct { 113 log logrus.FieldLogger 114 } 115 116 // OnBuildFilter builds a CEL expression filter. 117 func (t *CELExpressionFilter) OnBuildFilter(ctx context.Context, ff *flowpb.FlowFilter) ([]FilterFunc, error) { 118 if exprs := ff.GetExperimental().GetCelExpression(); exprs != nil { 119 filter, err := filterByCELExpression(ctx, t.log, exprs) 120 if err != nil { 121 return nil, err 122 } 123 return []FilterFunc{filter}, nil 124 } 125 return []FilterFunc{}, nil 126 }