diff --git a/cmd/main.go b/cmd/main.go index 551df1a95..ae5b56c14 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -177,7 +177,7 @@ func main() { if cfg.EnablePrometheusExporter { prometheusExporter = metricprometheus.NewPrometheusMetric() } else { - prometheusExporter = metricsmanager.NewMetricsMock() + prometheusExporter = metricsmanager.NewMetricsNoop() } // Create watchers diff --git a/go.mod b/go.mod index d5a5b412b..7a60f839e 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb github.com/go-openapi/strfmt v0.23.0 - github.com/google/cel-go v0.26.1 + github.com/google/cel-go v0.27.0 github.com/google/go-containerregistry v0.20.7 github.com/google/uuid v1.6.0 github.com/goradd/maps v1.3.0 @@ -42,7 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/panjf2000/ants/v2 v2.11.3 - github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12 + github.com/picatz/xcel v0.0.0-20260226001349-6958ffac5706 github.com/prometheus/alertmanager v0.27.0 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/procfs v0.19.2 @@ -70,7 +70,7 @@ require ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.121.3 // indirect cloud.google.com/go/auth v0.16.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -118,7 +118,7 @@ require ( github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect github.com/anchore/stereoscope v0.1.9 // indirect github.com/andybalholm/brotli v1.2.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.1 // indirect github.com/aquasecurity/go-version v0.0.1 // indirect @@ -367,7 +367,6 @@ require ( github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stripe/stripe-go/v74 v74.30.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -458,4 +457,4 @@ require ( zombiezen.com/go/sqlite v1.4.0 // indirect ) -replace github.com/inspektor-gadget/inspektor-gadget => github.com/matthyx/inspektor-gadget v0.0.0-20260226175242-c524fbad47d9 +replace github.com/inspektor-gadget/inspektor-gadget => github.com/matthyx/inspektor-gadget v0.0.0-20260228200201-44f8fa846146 diff --git a/go.sum b/go.sum index d81485b14..a3f0ccd97 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -741,8 +741,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -1233,8 +1233,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= @@ -1532,6 +1532,7 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matthyx/inspektor-gadget v0.0.0-20260226175242-c524fbad47d9 h1:5SElOPiaA2SKDGnLiWqocww+YagkLL9FPBBeMzKNTIg= github.com/matthyx/inspektor-gadget v0.0.0-20260226175242-c524fbad47d9/go.mod h1:V4TgEmWo37K72pQvC7XuRQssysrxIIkrNX4TtEkgiE0= +github.com/matthyx/inspektor-gadget v0.0.0-20260228200201-44f8fa846146/go.mod h1:V4TgEmWo37K72pQvC7XuRQssysrxIIkrNX4TtEkgiE0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -1729,8 +1730,8 @@ github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43/go.mod h1:pxMtw7c github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12 h1:RS7RxrC+OtnYpgI0li0NwvpE0cqYewsZGXUb6wAe0oQ= -github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12/go.mod h1:jxNaYyVlWe+WPV3G45KzlMLvplS3PQdHLUsFePIcaEg= +github.com/picatz/xcel v0.0.0-20260226001349-6958ffac5706 h1:xfPEUCHSHcjpu4WxgtC1lwBkP4Xa7R/pl8sXVF103Yg= +github.com/picatz/xcel v0.0.0-20260226001349-6958ffac5706/go.mod h1:bFTXcuU+280rICoGMpVTk/06XNfgvfeplhjWWoLKPys= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -1901,8 +1902,6 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/pkg/containerwatcher/v2/tracers/httpparse.go b/pkg/containerwatcher/v2/tracers/httpparse.go index d1c011cb4..8a8b3493c 100644 --- a/pkg/containerwatcher/v2/tracers/httpparse.go +++ b/pkg/containerwatcher/v2/tracers/httpparse.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httputil" "strconv" + "sync" "time" eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" @@ -29,6 +30,12 @@ var readSyscalls = map[string]bool{ "recvmsg": true, } +var bufReaderPool = sync.Pool{ + New: func() any { + return bufio.NewReaderSize(nil, 4096) + }, +} + var ConsistentHeaders = []string{ "Accept-Encoding", "Accept-Language", @@ -92,8 +99,10 @@ func ParseHttpRequest(data []byte) (*http.Request, error) { } // Parse headers only - bufReader := bufio.NewReader(bytes.NewReader(data[:headerEnd])) - req, err := http.ReadRequest(bufReader) + br := bufReaderPool.Get().(*bufio.Reader) + br.Reset(bytes.NewReader(data[:headerEnd])) + req, err := http.ReadRequest(br) + bufReaderPool.Put(br) if err != nil { return fallbackReadRequest(data) } @@ -136,8 +145,10 @@ func ParseHttpResponse(data []byte, req *http.Request) (*http.Response, error) { } // Parse headers only - bufReader := bufio.NewReader(bytes.NewReader(data[:headerEnd])) - resp, err := http.ReadResponse(bufReader, req) + br := bufReaderPool.Get().(*bufio.Reader) + br.Reset(bytes.NewReader(data[:headerEnd])) + resp, err := http.ReadResponse(br, req) + bufReaderPool.Put(br) if err != nil { return fallbackReadResponse(data, req) } diff --git a/pkg/metricsmanager/metrics_manager_noop.go b/pkg/metricsmanager/metrics_manager_noop.go new file mode 100644 index 000000000..952e3f682 --- /dev/null +++ b/pkg/metricsmanager/metrics_manager_noop.go @@ -0,0 +1,22 @@ +package metricsmanager + +import ( + "time" + + "github.com/kubescape/node-agent/pkg/utils" +) + +var _ MetricsManager = (*MetricsNoop)(nil) + +type MetricsNoop struct{} + +func NewMetricsNoop() *MetricsNoop { return &MetricsNoop{} } +func (m *MetricsNoop) Start() {} +func (m *MetricsNoop) Destroy() {} +func (m *MetricsNoop) ReportEvent(_ utils.EventType) {} +func (m *MetricsNoop) ReportFailedEvent() {} +func (m *MetricsNoop) ReportRuleProcessed(_ string) {} +func (m *MetricsNoop) ReportRuleAlert(_ string) {} +func (m *MetricsNoop) ReportRuleEvaluationTime(_ string, _ utils.EventType, _ time.Duration) {} +func (m *MetricsNoop) ReportContainerStart() {} +func (m *MetricsNoop) ReportContainerStop() {} \ No newline at end of file diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index 28201bde4..a9c88bb58 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -6,7 +6,9 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" "github.com/google/cel-go/ext" + "github.com/google/cel-go/interpreter" "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" "github.com/kubescape/node-agent/pkg/config" @@ -23,6 +25,52 @@ import ( "github.com/picatz/xcel" ) +// EventActivation implements interpreter.Activation for zero-allocation CEL evaluation. +type EventActivation struct { + eventType string + event *xcel.Object[utils.CelEvent] + isHTTP bool +} + +var eventActivationPool = sync.Pool{ + New: func() any { return &EventActivation{} }, +} + +var objectPool = sync.Pool{ + New: func() any { return &xcel.Object[utils.CelEvent]{} }, +} + +func (a *EventActivation) ResolveName(name string) (any, bool) { + switch name { + case "event": + return a.event, true + case "eventType": + return a.eventType, true + case "http": + if a.isHTTP { + return a.event, true + } + return nil, false + } + return nil, false +} + +func (a *EventActivation) Parent() interpreter.Activation { return nil } + +// Release returns the activation and its wrapped object to their pools. +func (a *EventActivation) Release() { + if w, ok := a.event.Raw.(*utils.HttpEventWrapper); ok { + w.CelEvent = nil + utils.HttpEventWrapperPool.Put(w) + } + a.event.Raw = nil + objectPool.Put(a.event) + a.event = nil + a.eventType = "" + a.isHTTP = false + eventActivationPool.Put(a) +} + var _ RuleEvaluator = (*CEL)(nil) type CEL struct { @@ -34,6 +82,7 @@ type CEL struct { ta xcel.TypeAdapter tp *xcel.TypeProvider eventConverters map[utils.EventType]func(utils.K8sEvent) utils.K8sEvent + optimizer *cel.StaticOptimizer } func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error) { @@ -42,11 +91,11 @@ func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error eventObj, eventTyp := xcel.NewObject(&utils.CelEventImpl{}) xcel.RegisterObject(ta, tp, eventObj, eventTyp, utils.CelFields) - // Register the nested request accessor type - requestObj, requestTyp := xcel.NewObject(utils.HttpRequestAccessor{}) - xcel.RegisterObject(ta, tp, requestObj, requestTyp, utils.HttpRequestFields) - - // Set the request field's type now that requestTyp is available + // Register the nested request type (HttpEventWrapper implements ref.Val directly) + requestTyp := cel.ObjectType("HttpEventWrapper", traits.ReceiverType) + xcel.RegisterType(tp, requestTyp) + xcel.RegisterStructType(tp, requestTyp.TypeName(), utils.HttpRequestFields) + utils.SetHttpRequestType(requestTyp) utils.CelFields["request"].Type = requestTyp envOptions := []cel.EnvOption{ @@ -56,6 +105,7 @@ func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error cel.CustomTypeAdapter(ta), cel.CustomTypeProvider(tp), ext.Strings(), + ext.Bindings(), k8s.K8s(objectCache.K8sObjectCache(), cfg), applicationprofile.AP(objectCache, cfg), networkneighborhood.NN(objectCache, cfg), @@ -68,6 +118,14 @@ func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error if err != nil { return nil, err } + folder, err := cel.NewConstantFoldingOptimizer() + if err != nil { + return nil, fmt.Errorf("failed to create constant folding optimizer: %w", err) + } + optimizer, err := cel.NewStaticOptimizer(folder) + if err != nil { + return nil, fmt.Errorf("failed to create static optimizer: %w", err) + } c := &CEL{ env: env, objectCache: objectCache, @@ -75,6 +133,7 @@ func NewCEL(objectCache objectcache.ObjectCache, cfg config.Config) (*CEL, error ta: ta, tp: tp, eventConverters: make(map[utils.EventType]func(utils.K8sEvent) utils.K8sEvent), + optimizer: optimizer, } return c, nil @@ -97,6 +156,14 @@ func (c *CEL) registerExpression(expression string) error { return fmt.Errorf("failed to compile expression: %s", issues.Err()) } + if optimizedAst, optIssues := c.optimizer.Optimize(c.env, ast); optIssues == nil || optIssues.Err() == nil { + ast = optimizedAst + } else { + logger.L().Warning("CEL static optimizer failed, using unoptimized AST", + helpers.String("expression", expression), + helpers.Error(optIssues.Err())) + } + program, err := c.env.Program(ast, cel.EvalOptions(cel.OptOptimize)) if err != nil { // Cache nil to prevent repeated program creation attempts @@ -128,33 +195,36 @@ func (c *CEL) getOrCreateProgram(expression string) (cel.Program, error) { return program, nil } -func (c *CEL) createEvalContext(event *events.EnrichedEvent) map[string]any { - eventType := event.Event.GetEventType() +func (c *CEL) CreateEvalContext(event utils.K8sEvent) *EventActivation { + eventType := event.GetEventType() - // Apply event converter if one is registered, otherwise cast to CelEvent - var obj interface{} + obj := objectPool.Get().(*xcel.Object[utils.CelEvent]) + var celEvent utils.CelEvent if converter, exists := c.eventConverters[eventType]; exists { - obj, _ = xcel.NewObject(converter(event.Event)) + celEvent = converter(event).(utils.CelEvent) } else { - obj, _ = xcel.NewObject(event.Event.(utils.CelEvent)) - } - - evalContext := map[string]any{ - "eventType": string(eventType), - "event": obj, + celEvent = event.(utils.CelEvent) } - // For HTTP events, also add "http" variable if eventType == utils.HTTPEventType { - evalContext["http"] = obj + wrapper := utils.HttpEventWrapperPool.Get().(*utils.HttpEventWrapper) + wrapper.CelEvent = celEvent + obj.Raw = wrapper + } else { + obj.Raw = celEvent } - return evalContext + activation := eventActivationPool.Get().(*EventActivation) + activation.eventType = string(eventType) + activation.event = obj + activation.isHTTP = eventType == utils.HTTPEventType + + return activation } // evaluateProgramWithContext compiles (or retrieves cached) and evaluates a CEL expression // with the provided evaluation context, returning the CEL result value -func (c *CEL) evaluateProgramWithContext(expression string, evalContext map[string]any) (ref.Val, error) { +func (c *CEL) evaluateProgramWithContext(expression string, evalContext *EventActivation) (ref.Val, error) { program, err := c.getOrCreateProgram(expression) if err != nil { return nil, err @@ -175,15 +245,8 @@ func (c *CEL) evaluateProgramWithContext(expression string, evalContext map[stri return out, nil } -func (c *CEL) EvaluateRule(event *events.EnrichedEvent, expressions []typesv1.RuleExpression) (bool, error) { - eventType := event.Event.GetEventType() - evalContext := c.createEvalContext(event) - +func (c *CEL) EvaluateRuleWithContext(evalContext *EventActivation, expressions []typesv1.RuleExpression) (bool, error) { for _, expression := range expressions { - if expression.EventType != eventType { - continue - } - out, err := c.evaluateProgramWithContext(expression.Expression, evalContext) if err != nil { return false, err @@ -206,9 +269,7 @@ func (c *CEL) EvaluateRule(event *events.EnrichedEvent, expressions []typesv1.Ru return true, nil } -func (c *CEL) EvaluateExpression(event *events.EnrichedEvent, expression string) (string, error) { - evalContext := c.createEvalContext(event) - +func (c *CEL) EvaluateExpressionWithContext(evalContext *EventActivation, expression string) (string, error) { out, err := c.evaluateProgramWithContext(expression, evalContext) if err != nil { return "", err @@ -226,6 +287,18 @@ func (c *CEL) EvaluateExpression(event *events.EnrichedEvent, expression string) return strVal, nil } +func (c *CEL) EvaluateRule(event *events.EnrichedEvent, expressions []typesv1.RuleExpression) (bool, error) { + evalContext := c.CreateEvalContext(event.Event) + defer evalContext.Release() + return c.EvaluateRuleWithContext(evalContext, expressions) +} + +func (c *CEL) EvaluateExpression(event *events.EnrichedEvent, expression string) (string, error) { + evalContext := c.CreateEvalContext(event.Event) + defer evalContext.Release() + return c.EvaluateExpressionWithContext(evalContext, expression) +} + func (c *CEL) RegisterHelper(function cel.EnvOption) error { extendedEnv, err := c.env.Extend(function) if err != nil { diff --git a/pkg/rulemanager/cel/cel_interface.go b/pkg/rulemanager/cel/cel_interface.go index 500c3cbe1..070a3a178 100644 --- a/pkg/rulemanager/cel/cel_interface.go +++ b/pkg/rulemanager/cel/cel_interface.go @@ -8,9 +8,18 @@ import ( ) type RuleEvaluator interface { + // EvaluateRule evaluates rules for a single call. For repeated evaluation of the same event, + // use CreateEvalContext once and call EvaluateRuleWithContext for each rule instead. EvaluateRule(event *events.EnrichedEvent, expressions []typesv1.RuleExpression) (bool, error) + // EvaluateExpression evaluates an expression for a single call. For repeated evaluation of the same event, + // use CreateEvalContext once and call EvaluateExpressionWithContext instead. EvaluateExpression(event *events.EnrichedEvent, expression string) (string, error) + RegisterHelper(function cel.EnvOption) error RegisterCustomType(eventType utils.EventType, obj interface{}) error RegisterEventConverter(eventType utils.EventType, converter func(utils.K8sEvent) utils.K8sEvent) + + CreateEvalContext(event utils.K8sEvent) *EventActivation + EvaluateRuleWithContext(evalContext *EventActivation, expressions []typesv1.RuleExpression) (bool, error) + EvaluateExpressionWithContext(evalContext *EventActivation, expression string) (string, error) } diff --git a/pkg/rulemanager/cel/cel_test.go b/pkg/rulemanager/cel/cel_test.go new file mode 100644 index 000000000..c20aea3cc --- /dev/null +++ b/pkg/rulemanager/cel/cel_test.go @@ -0,0 +1,93 @@ +//go:build linux + +package cel + +import ( + "testing" + + "github.com/goradd/maps" + "github.com/kubescape/node-agent/pkg/config" + "github.com/kubescape/node-agent/pkg/objectcache" + objectcachev1 "github.com/kubescape/node-agent/pkg/objectcache/v1" + typesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1" + "github.com/kubescape/node-agent/pkg/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newTestCEL(t *testing.T) *CEL { + t.Helper() + objCache := objectcachev1.RuleObjectCacheMock{ + ContainerIDToSharedData: maps.NewSafeMap[string, *objectcache.WatchedContainerData](), + } + c, err := NewCEL(&objCache, config.Config{}) + require.NoError(t, err) + return c +} + +func TestConstantFoldingExpressions(t *testing.T) { + c := newTestCEL(t) + + tests := []struct { + name string + expression string + event utils.K8sEvent + want bool + }{ + { + name: "uint cast folds correctly - match", + expression: "event.cmd == uint(5)", + event: &utils.StructEvent{ + EventType: utils.ExecveEventType, + Cmd: 5, + }, + want: true, + }, + { + name: "uint cast folds correctly - no match", + expression: "event.cmd == uint(5)", + event: &utils.StructEvent{ + EventType: utils.ExecveEventType, + Cmd: 99, + }, + want: false, + }, + { + name: "uint in list literal - first element match", + expression: "event.cmd in [uint(22), uint(2022)]", + event: &utils.StructEvent{ + EventType: utils.ExecveEventType, + Cmd: 22, + }, + want: true, + }, + { + name: "uint in list literal - second element match", + expression: "event.cmd in [uint(22), uint(2022)]", + event: &utils.StructEvent{ + EventType: utils.ExecveEventType, + Cmd: 2022, + }, + want: true, + }, + { + name: "uint in list literal - no match", + expression: "event.cmd in [uint(22), uint(2022)]", + event: &utils.StructEvent{ + EventType: utils.ExecveEventType, + Cmd: 7, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := c.CreateEvalContext(tt.event) + defer ctx.Release() + got, err := c.EvaluateRuleWithContext(ctx, []typesv1.RuleExpression{{Expression: tt.expression}}) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/rulemanager/cel/libraries/parse/parse.go b/pkg/rulemanager/cel/libraries/parse/parse.go index ba82f982f..80de653dd 100644 --- a/pkg/rulemanager/cel/libraries/parse/parse.go +++ b/pkg/rulemanager/cel/libraries/parse/parse.go @@ -1,6 +1,8 @@ package parse import ( + "strings" + "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/kubescape/node-agent/pkg/rulemanager/cel/libraries/celparse" @@ -25,3 +27,15 @@ func (l *parseLibrary) getExecPath(args ref.Val, comm ref.Val) ref.Val { } return types.String(commStr) } + +func (l *parseLibrary) basename(path ref.Val) ref.Val { + s, ok := path.Value().(string) + if !ok { + return types.MaybeNoSuchOverloadErr(path) + } + idx := strings.LastIndex(s, "/") + if idx == -1 { + return types.String(s) + } + return types.String(s[idx+1:]) +} diff --git a/pkg/rulemanager/cel/libraries/parse/parselib.go b/pkg/rulemanager/cel/libraries/parse/parselib.go index 57b05be45..bd3f9876d 100644 --- a/pkg/rulemanager/cel/libraries/parse/parselib.go +++ b/pkg/rulemanager/cel/libraries/parse/parselib.go @@ -48,6 +48,12 @@ func (l *parseLibrary) Declarations() map[string][]cel.FunctionOpt { }), ), }, + "parse.basename": { + cel.Overload( + "parse_basename", []*cel.Type{cel.StringType}, cel.StringType, + cel.UnaryBinding(l.basename), + ), + }, } } @@ -76,6 +82,9 @@ func (e *parseCostEstimator) EstimateCallCost(function, overloadID string, targe case "parse.get_exec_path": // List parsing + simple array access + string comparison - O(1) operation cost = 5 + case "parse.basename": + // Single string scan for last '/' - O(n) on path length + cost = 1 } return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: uint64(cost), Max: uint64(cost)}} } diff --git a/pkg/rulemanager/cel/libraries/parse/parsing_test.go b/pkg/rulemanager/cel/libraries/parse/parsing_test.go index 5677c8b56..ba07fac95 100644 --- a/pkg/rulemanager/cel/libraries/parse/parsing_test.go +++ b/pkg/rulemanager/cel/libraries/parse/parsing_test.go @@ -42,6 +42,26 @@ func TestParseLibrary(t *testing.T) { expr: "parse.get_exec_path(['/usr/bin/python'], 'python')", expected: "/usr/bin/python", }, + { + name: "basename with full path", + expr: "parse.basename('/usr/bin/nmap')", + expected: "nmap", + }, + { + name: "basename with just filename", + expr: "parse.basename('nmap')", + expected: "nmap", + }, + { + name: "basename with trailing slash", + expr: "parse.basename('/usr/bin/')", + expected: "", + }, + { + name: "basename with root path", + expr: "parse.basename('/nmap')", + expected: "nmap", + }, } for _, tt := range tests { diff --git a/pkg/rulemanager/rule_manager.go b/pkg/rulemanager/rule_manager.go index 75241fcc2..058e001a0 100644 --- a/pkg/rulemanager/rule_manager.go +++ b/pkg/rulemanager/rule_manager.go @@ -3,7 +3,8 @@ package rulemanager import ( "context" "crypto/md5" - "fmt" + "encoding/hex" + "strconv" "time" "github.com/armosec/armoapi-go/armotypes" @@ -189,9 +190,7 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) return } - if !isSupportedEventType(rules, enrichedEvent) { - return - } + eventType := enrichedEvent.Event.GetEventType() _, apChecksum, err := profilehelper.GetContainerApplicationProfile(rm.objectCache, enrichedEvent.ContainerID) profileExists = err == nil @@ -201,12 +200,20 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) return } - eventType := enrichedEvent.Event.GetEventType() + evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent.Event) + defer evalContext.Release() + for _, rule := range rules { if !rule.Enabled { continue } + // Fast path: skip rules that have no expressions for this event type + ruleExpressions, ok := rule.ExpressionsByEventType[eventType] + if !ok { + continue + } + if !RuleAppliesToContext(&rule, enrichedEvent.SourceContext) { continue } @@ -216,17 +223,12 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) continue } - ruleExpressions := rm.getRuleExpressions(rule, eventType) - if len(ruleExpressions) == 0 { - continue - } - if rule.SupportPolicy && rm.validateRulePolicy(rule, enrichedEvent.Event, enrichedEvent.ContainerID) { continue } startTime := time.Now() - shouldAlert, err := rm.celEvaluator.EvaluateRule(enrichedEvent, rule.Expressions.RuleExpression) + shouldAlert, err := rm.celEvaluator.EvaluateRuleWithContext(evalContext, ruleExpressions) evaluationTime := time.Since(startTime) rm.metrics.ReportRuleEvaluationTime(rule.Name, eventType, evaluationTime) @@ -238,10 +240,10 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) if shouldAlert { state := rule.State if eventType == utils.HTTPEventType { // TODO: Manage state evaluation in a better way (this is abuse of the state map, we need a better way to pass payloads from rules.) - state = rm.evaluateHTTPPayloadState(rule.State, enrichedEvent) + state = rm.evaluateHTTPPayloadState(rule.State, evalContext) } rm.metrics.ReportRuleAlert(rule.Name) - message, uniqueID, err := rm.getUniqueIdAndMessage(enrichedEvent, rule) + message, uniqueID, err := rm.getUniqueIdAndMessage(evalContext, rule) if err != nil { logger.L().Error("RuleManager - failed to get unique ID and message", helpers.Error(err)) continue @@ -282,7 +284,7 @@ func (rm *RuleManager) enrichEventWithContext(enrichedEvent *events.EnrichedEven if contextInfo, found := rm.mntnsRegistry.Lookup(mntnsID); found { enrichedEvent.SourceContext = contextInfo logger.L().Debug("RuleManager - enriched event with context", - helpers.String("mntns", fmt.Sprintf("%d", mntnsID)), + helpers.String("mntns", strconv.FormatUint(mntnsID, 10)), helpers.String("context", string(contextInfo.Context()))) } } @@ -333,24 +335,26 @@ func (rm *RuleManager) IsPodMonitored(namespace, pod string) bool { } func (rm *RuleManager) EvaluatePolicyRulesForEvent(eventType utils.EventType, event utils.K8sEvent) []string { - results := []string{} + var results []string creator := rm.ruleBindingCache.GetRuleCreator() rules := creator.CreateRulePolicyRulesByEventType(eventType) + evalContext := rm.celEvaluator.CreateEvalContext(event) + defer evalContext.Release() + for _, rule := range rules { if !rule.SupportPolicy { continue } - enrichedEvent := &events.EnrichedEvent{Event: event} - ruleExpressions := rm.getRuleExpressions(rule, eventType) - if len(ruleExpressions) == 0 { + ruleExpressions, ok := rule.ExpressionsByEventType[eventType] + if !ok { continue } startTime := time.Now() - shouldAlert, err := rm.celEvaluator.EvaluateRule(enrichedEvent, ruleExpressions) + shouldAlert, err := rm.celEvaluator.EvaluateRuleWithContext(evalContext, ruleExpressions) evaluationTime := time.Since(startTime) rm.metrics.ReportRuleEvaluationTime(rule.ID, eventType, evaluationTime) @@ -382,22 +386,12 @@ func (rm *RuleManager) validateRulePolicy(rule typesv1.Rule, event utils.K8sEven return allowed } -func (rm *RuleManager) getRuleExpressions(rule typesv1.Rule, eventType utils.EventType) []typesv1.RuleExpression { - var ruleExpressions []typesv1.RuleExpression - for _, expression := range rule.Expressions.RuleExpression { - if string(expression.EventType) == string(eventType) { - ruleExpressions = append(ruleExpressions, expression) - } - } - return ruleExpressions -} - -func (rm *RuleManager) getUniqueIdAndMessage(enrichedEvent *events.EnrichedEvent, rule typesv1.Rule) (string, string, error) { - message, err := rm.celEvaluator.EvaluateExpression(enrichedEvent, rule.Expressions.Message) +func (rm *RuleManager) getUniqueIdAndMessage(evalContext *cel.EventActivation, rule typesv1.Rule) (string, string, error) { + message, err := rm.celEvaluator.EvaluateExpressionWithContext(evalContext, rule.Expressions.Message) if err != nil { logger.L().Error("RuleManager - failed to evaluate message", helpers.Error(err)) } - uniqueID, err := rm.celEvaluator.EvaluateExpression(enrichedEvent, rule.Expressions.UniqueID) + uniqueID, err := rm.celEvaluator.EvaluateExpressionWithContext(evalContext, rule.Expressions.UniqueID) if err != nil { logger.L().Error("RuleManager - failed to evaluate unique ID", helpers.Error(err)) } @@ -407,31 +401,18 @@ func (rm *RuleManager) getUniqueIdAndMessage(enrichedEvent *events.EnrichedEvent return message, uniqueID, err } -func isSupportedEventType(rules []typesv1.Rule, enrichedEvent *events.EnrichedEvent) bool { - eventType := enrichedEvent.Event.GetEventType() - for _, rule := range rules { - for _, expression := range rule.Expressions.RuleExpression { - if string(expression.EventType) == string(eventType) { - return true - } - } - } - return false -} - func hashStringToMD5(str string) string { hash := md5.Sum([]byte(str)) - hashString := fmt.Sprintf("%x", hash) - return hashString + return hex.EncodeToString(hash[:]) } -func (rm *RuleManager) evaluateHTTPPayloadState(state map[string]any, enrichedEvent *events.EnrichedEvent) map[string]any { +func (rm *RuleManager) evaluateHTTPPayloadState(state map[string]any, evalContext *cel.EventActivation) map[string]any { payloadExpression, ok := state["payload"].(string) if !ok || payloadExpression == "" { return state } - payloadValue, err := rm.celEvaluator.EvaluateExpression(enrichedEvent, payloadExpression) + payloadValue, err := rm.celEvaluator.EvaluateExpressionWithContext(evalContext, payloadExpression) if err != nil { logger.L().Error("RuleManager - failed to evaluate http payload expression", helpers.Error(err)) return state diff --git a/pkg/rulemanager/rulecreator/factory.go b/pkg/rulemanager/rulecreator/factory.go index 7ad18cfc2..5e63d72eb 100644 --- a/pkg/rulemanager/rulecreator/factory.go +++ b/pkg/rulemanager/rulecreator/factory.go @@ -51,6 +51,7 @@ func (r *RuleCreatorImpl) CreateRuleByName(name string) typesv1.Rule { } func (r *RuleCreatorImpl) RegisterRule(rule typesv1.Rule) { + rule.Init() r.Rules = append(r.Rules, rule) } @@ -105,8 +106,9 @@ func (r *RuleCreatorImpl) SyncRules(newRules []typesv1.Rule) { // Create a map of new rules by ID for quick lookup newRuleMap := make(map[string]typesv1.Rule) - for _, rule := range newRules { - newRuleMap[rule.ID] = rule + for i := range newRules { + newRules[i].Init() + newRuleMap[newRules[i].ID] = newRules[i] } // Remove rules that are no longer present @@ -148,6 +150,8 @@ func (r *RuleCreatorImpl) UpdateRule(rule typesv1.Rule) bool { r.mutex.Lock() defer r.mutex.Unlock() + rule.Init() + for i, existingRule := range r.Rules { if existingRule.ID == rule.ID { r.Rules[i] = rule diff --git a/pkg/rulemanager/types/v1/types.go b/pkg/rulemanager/types/v1/types.go index e91f28238..42ecf98b7 100644 --- a/pkg/rulemanager/types/v1/types.go +++ b/pkg/rulemanager/types/v1/types.go @@ -32,6 +32,19 @@ type Rule struct { IsTriggerAlert bool `json:"isTriggerAlert" yaml:"isTriggerAlert"` MitreTactic string `json:"mitreTactic" yaml:"mitreTactic"` MitreTechnique string `json:"mitreTechnique" yaml:"mitreTechnique"` + + // Pre-computed index: event type → expressions for that type. + // Populated by Init(). Avoids per-event scanning and allocation. + ExpressionsByEventType map[utils.EventType][]RuleExpression `json:"-" yaml:"-"` +} + +// Init pre-computes the ExpressionsByEventType index. +// Must be called after a Rule is created or updated. +func (r *Rule) Init() { + r.ExpressionsByEventType = make(map[utils.EventType][]RuleExpression, len(r.Expressions.RuleExpression)) + for _, expr := range r.Expressions.RuleExpression { + r.ExpressionsByEventType[expr.EventType] = append(r.ExpressionsByEventType[expr.EventType], expr) + } } type RuleExpressions struct { diff --git a/pkg/utils/cel.go b/pkg/utils/cel.go index 58ebea686..cdcc7d1be 100644 --- a/pkg/utils/cel.go +++ b/pkg/utils/cel.go @@ -2,9 +2,12 @@ package utils import ( "bytes" + "errors" "fmt" "io" "net/http" + "reflect" + "sync" celtypes "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -31,10 +34,53 @@ type CelEventImpl struct { CelEvent } -// HttpRequestAccessor provides access to HTTP request fields -// It's a lightweight wrapper around CelEvent that avoids allocations -type HttpRequestAccessor struct { - HttpEvent CelEvent +var errCelObjectNil = errors.New("celval: object is nil") + +var httpRequestType *celtypes.Type + +// SetHttpRequestType sets the CEL type used by HttpEventWrapper. +func SetHttpRequestType(t *celtypes.Type) { + httpRequestType = t +} + +// HttpEventWrapper wraps a CelEvent for HTTP request field dispatch. +// It implements ref.Val so it can be returned directly from the "request" +// field getter without allocating an xcel.Object on every access. +type HttpEventWrapper struct { + CelEvent +} + +var HttpEventWrapperPool = sync.Pool{ + New: func() any { return &HttpEventWrapper{} }, +} + +func (w *HttpEventWrapper) ConvertToNative(typeDesc reflect.Type) (any, error) { + if typeDesc == reflect.TypeOf(w) { + return w, nil + } + return nil, fmt.Errorf("unsupported conversion to %v", typeDesc) +} + +func (w *HttpEventWrapper) ConvertToType(typeValue ref.Type) ref.Val { + if typeValue == w.Type() { + return w + } + return celtypes.NewErr("type conversion error") +} + +func (w *HttpEventWrapper) Equal(other ref.Val) ref.Val { + return celtypes.Bool(other == w) +} + +func (w *HttpEventWrapper) Type() ref.Type { + if httpRequestType == nil { + return celtypes.ErrType + } + return httpRequestType +} + +func (w *HttpEventWrapper) Value() any { + return w } var isSet = ref.FieldTester(func(target any) bool { @@ -45,21 +91,26 @@ var isSet = ref.FieldTester(func(target any) bool { return true }) -var requestIsSet = ref.FieldTester(func(target any) bool { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { +var requestFieldIsSet = ref.FieldTester(func(target any) bool { + x := target.(*xcel.Object[CelEvent]) + if x.Raw == nil { return false } - req := x.Raw.HttpEvent.GetRequest() - return req != nil + _, ok := x.Raw.(*HttpEventWrapper) + return ok +}) + +var requestIsSet = ref.FieldTester(func(target any) bool { + x, ok := target.(*HttpEventWrapper) + return ok && x.CelEvent != nil && x.GetRequest() != nil }) var urlIsSet = ref.FieldTester(func(target any) bool { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { + x, ok := target.(*HttpEventWrapper) + if !ok || x.CelEvent == nil { return false } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req == nil || req.URL == nil { return false } @@ -73,7 +124,7 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } return x.Raw.GetArgs(), nil }), @@ -84,9 +135,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetAttrSize(), nil + return celtypes.Uint(x.Raw.GetAttrSize()), nil }), }, "capName": { @@ -95,9 +146,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetCapability(), nil + return celtypes.String(x.Raw.GetCapability()), nil }), }, "cmd": { @@ -106,9 +157,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetCmd(), nil + return celtypes.Uint(x.Raw.GetCmd()), nil }), }, "comm": { @@ -117,9 +168,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetComm(), nil + return celtypes.String(x.Raw.GetComm()), nil }), }, "containerId": { @@ -128,9 +179,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetContainerID(), nil + return celtypes.String(x.Raw.GetContainerID()), nil }), }, "containerName": { @@ -139,9 +190,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetContainer(), nil + return celtypes.String(x.Raw.GetContainer()), nil }), }, "cwd": { @@ -150,9 +201,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetCwd(), nil + return celtypes.String(x.Raw.GetCwd()), nil }), }, "dstAddr": { @@ -161,9 +212,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetDstEndpoint().Addr, nil + return celtypes.String(x.Raw.GetDstEndpoint().Addr), nil }), }, "dstIp": { @@ -172,9 +223,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetDstIP(), nil + return celtypes.String(x.Raw.GetDstIP()), nil }), }, "dstPort": { @@ -183,9 +234,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return int(x.Raw.GetDstPort()), nil + return celtypes.Int(x.Raw.GetDstPort()), nil }), }, "exepath": { @@ -194,9 +245,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetExePath(), nil + return celtypes.String(x.Raw.GetExePath()), nil }), }, "flags": { @@ -205,7 +256,7 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } return x.Raw.GetFlags(), nil }), @@ -216,9 +267,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return int(x.Raw.GetFlagsRaw()), nil + return celtypes.Int(x.Raw.GetFlagsRaw()), nil }), }, "module": { @@ -227,9 +278,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetModule(), nil + return celtypes.String(x.Raw.GetModule()), nil }), }, "name": { @@ -238,9 +289,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetDNSName(), nil + return celtypes.String(x.Raw.GetDNSName()), nil }), }, "namespace": { @@ -249,9 +300,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetNamespace(), nil + return celtypes.String(x.Raw.GetNamespace()), nil }), }, "newPath": { @@ -260,9 +311,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetNewPath(), nil + return celtypes.String(x.Raw.GetNewPath()), nil }), }, "oldPath": { @@ -271,9 +322,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetOldPath(), nil + return celtypes.String(x.Raw.GetOldPath()), nil }), }, "opcode": { @@ -282,9 +333,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetOpcode(), nil + return celtypes.Int(x.Raw.GetOpcode()), nil }), }, "path": { @@ -293,9 +344,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPath(), nil + return celtypes.String(x.Raw.GetPath()), nil }), }, "pcomm": { @@ -304,9 +355,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPcomm(), nil + return celtypes.String(x.Raw.GetPcomm()), nil }), }, "pid": { @@ -315,9 +366,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPID(), nil + return celtypes.Uint(x.Raw.GetPID()), nil }), }, "pktType": { @@ -326,9 +377,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPktType(), nil + return celtypes.String(x.Raw.GetPktType()), nil }), }, "podName": { @@ -337,9 +388,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPod(), nil + return celtypes.String(x.Raw.GetPod()), nil }), }, "ppid": { @@ -348,9 +399,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPpid(), nil + return celtypes.Uint(x.Raw.GetPpid()), nil }), }, "proto": { @@ -359,9 +410,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetProto(), nil + return celtypes.String(x.Raw.GetProto()), nil }), }, "pupperlayer": { @@ -370,9 +421,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetPupperLayer(), nil + return celtypes.Bool(x.Raw.GetPupperLayer()), nil }), }, "srcPort": { @@ -381,9 +432,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return int(x.Raw.GetSrcPort()), nil + return celtypes.Int(x.Raw.GetSrcPort()), nil }), }, "syscallName": { @@ -392,9 +443,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetSyscall(), nil + return celtypes.String(x.Raw.GetSyscall()), nil }), }, "upperlayer": { @@ -403,9 +454,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetUpperLayer(), nil + return celtypes.Bool(x.Raw.GetUpperLayer()), nil }), }, "uid": { @@ -414,24 +465,28 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return x.Raw.GetUid(), nil + uid := x.Raw.GetUid() + if uid == nil { + return celtypes.Uint(0), nil + } + return celtypes.Uint(*uid), nil }), }, - // HTTP request nested object (no allocation - just wraps the event) "request": { - Type: nil, // Will be set during registration - IsSet: isSet, + Type: nil, // Set during registration + IsSet: requestFieldIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil + } + w, ok := x.Raw.(*HttpEventWrapper) + if !ok { + return nil, errCelObjectNil } - // Return a wrapped accessor - xcel.NewObject is lightweight (just pointer wrapping) - accessor := HttpRequestAccessor{HttpEvent: x.Raw} - obj, _ := xcel.NewObject(accessor) - return obj, nil + return w, nil }), }, "direction": { @@ -440,9 +495,9 @@ var CelFields = map[string]*celtypes.FieldType{ GetFrom: ref.FieldGetter(func(target any) (any, error) { x := target.(*xcel.Object[CelEvent]) if x.Raw == nil { - return nil, fmt.Errorf("celval: object is nil") + return nil, errCelObjectNil } - return string(x.Raw.GetDirection()), nil + return celtypes.String(x.Raw.GetDirection()), nil }), }, } @@ -453,11 +508,11 @@ var HttpRequestFields = map[string]*celtypes.FieldType{ Type: celtypes.MapType, IsSet: requestIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil { return req.Header, nil } @@ -468,77 +523,77 @@ var HttpRequestFields = map[string]*celtypes.FieldType{ Type: celtypes.StringType, IsSet: requestIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil { - return req.Host, nil + return celtypes.String(req.Host), nil } - return "", nil + return celtypes.String(""), nil }), }, "method": { Type: celtypes.StringType, IsSet: requestIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil { - return req.Method, nil + return celtypes.String(req.Method), nil } - return "", nil + return celtypes.String(""), nil }), }, "url": { Type: celtypes.StringType, IsSet: urlIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil && req.URL != nil { - return req.URL.String(), nil + return celtypes.String(req.URL.String()), nil } - return "", nil + return celtypes.String(""), nil }), }, "path": { Type: celtypes.StringType, IsSet: urlIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil && req.URL != nil { - return req.URL.Path, nil + return celtypes.String(req.URL.Path), nil } - return "", nil + return celtypes.String(""), nil }), }, "body": { Type: celtypes.StringType, IsSet: requestIsSet, GetFrom: ref.FieldGetter(func(target any) (any, error) { - x := target.(*xcel.Object[HttpRequestAccessor]) - if x.Raw.HttpEvent == nil { - return nil, fmt.Errorf("celval: object is nil") + x := target.(*HttpEventWrapper) + if x.CelEvent == nil { + return nil, errCelObjectNil } // Try GetBuf() first (for eBPF events) - buf := x.Raw.HttpEvent.GetBuf() + buf := x.GetBuf() if len(buf) > 0 { - return string(buf), nil + return celtypes.String(buf), nil } // Fallback to reading from Request.Body (for test events) - req := x.Raw.HttpEvent.GetRequest() + req := x.GetRequest() if req != nil && req.Body != nil { // Read with size limit (10MB) and restore body for downstream readers const maxBodySize = 10 * 1024 * 1024 // 10MB @@ -547,11 +602,11 @@ var HttpRequestFields = map[string]*celtypes.FieldType{ req.Body.Close() // Close the original body req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) // Restore for downstream if err != nil { - return "", err + return celtypes.String(""), err } - return string(bodyBytes), nil + return celtypes.String(bodyBytes), nil } - return "", nil + return celtypes.String(""), nil }), }, }