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  }