From 03898bd2fccfc66a500eda135aef8ef8a32c3f7d Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Tue, 10 Feb 2026 15:28:28 +0200 Subject: [PATCH 01/12] optimize CreateEventFromRequest and MakeHttpEvent performance Signed-off-by: Yakir Oren --- pkg/containerwatcher/v2/tracers/httpparse.go | 23 +++++--------------- pkg/utils/datasource_event.go | 3 +-- pkg/utils/events.go | 6 +++++ pkg/utils/struct_event.go | 10 ++++----- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/pkg/containerwatcher/v2/tracers/httpparse.go b/pkg/containerwatcher/v2/tracers/httpparse.go index d1c011cb4..398a1b876 100644 --- a/pkg/containerwatcher/v2/tracers/httpparse.go +++ b/pkg/containerwatcher/v2/tracers/httpparse.go @@ -15,20 +15,6 @@ import ( "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" ) -var writeSyscalls = map[string]bool{ - "write": true, - "writev": true, - "sendto": true, - "sendmsg": true, -} - -var readSyscalls = map[string]bool{ - "read": true, - "readv": true, - "recvfrom": true, - "recvmsg": true, -} - var ConsistentHeaders = []string{ "Accept-Encoding", "Accept-Language", @@ -43,7 +29,7 @@ func CreateEventFromRequest(bpfEvent utils.HttpRawEvent) (utils.HttpEvent, error return nil, err } - direction, err := GetPacketDirection(bpfEvent.GetSyscall()) + request, err := ParseHttpRequest(FromCString(bpfEvent.GetBuf())) if err != nil { return nil, err } @@ -69,11 +55,12 @@ func ExtractConsistentHeaders(headers http.Header) map[string][]string { } func GetPacketDirection(syscall string) (consts.NetworkDirection, error) { - if readSyscalls[syscall] { + switch syscall { + case "read", "readv", "recvfrom", "recvmsg": return consts.Inbound, nil - } else if writeSyscalls[syscall] { + case "write", "writev", "sendto", "sendmsg": return consts.Outbound, nil - } else { + default: return "", fmt.Errorf("unknown syscall %s", syscall) } } diff --git a/pkg/utils/datasource_event.go b/pkg/utils/datasource_event.go index 55abaca20..eac24a878 100644 --- a/pkg/utils/datasource_event.go +++ b/pkg/utils/datasource_event.go @@ -2,7 +2,6 @@ package utils import ( "errors" - "net" "net/http" "os" "strings" @@ -761,7 +760,7 @@ func (e *DatasourceEvent) MakeHttpEvent(request *http.Request, direction consts. Datasource: e.Datasource, Direction: direction, EventType: e.EventType, - Internal: func() bool { ip := net.ParseIP(e.GetOtherIp()); return ip != nil && ip.IsPrivate() }(), + Internal: isPrivateIP(e.GetOtherIp()), Request: request, Response: e.Response, Syscall: e.Syscall, diff --git a/pkg/utils/events.go b/pkg/utils/events.go index fda2e5a66..8d286ffe0 100644 --- a/pkg/utils/events.go +++ b/pkg/utils/events.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "net" "net/http" "path/filepath" @@ -9,6 +10,11 @@ import ( "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" ) +func isPrivateIP(ipStr string) bool { + ip := net.ParseIP(ipStr) + return ip != nil && ip.IsPrivate() +} + type HTTPDataType int const ( diff --git a/pkg/utils/struct_event.go b/pkg/utils/struct_event.go index 278c8ebe8..8c39376fd 100644 --- a/pkg/utils/struct_event.go +++ b/pkg/utils/struct_event.go @@ -1,7 +1,6 @@ package utils import ( - "net" "net/http" "time" @@ -433,11 +432,10 @@ func (e *StructEvent) IsDir() bool { } func (e *StructEvent) MakeHttpEvent(request *http.Request, direction consts.NetworkDirection) HttpEvent { - event := *e - event.Request = request - event.Direction = direction - event.Internal = func() bool { ip := net.ParseIP(e.GetOtherIp()); return ip != nil && ip.IsPrivate() }() - return &event + e.Request = request + e.Direction = direction + e.Internal = isPrivateIP(e.GetOtherIp()) + return e } func (e *StructEvent) Release() {} From 6f5da293f95084140bf85aab2da2c8c6a6bd57c1 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Thu, 12 Feb 2026 17:04:23 +0200 Subject: [PATCH 02/12] add parse.basename CEL function and enable cel.bind macro Signed-off-by: Yakir Oren --- pkg/rulemanager/cel/cel.go | 34 ++++--- pkg/rulemanager/cel/cel_interface.go | 5 + pkg/rulemanager/cel/libraries/parse/parse.go | 14 +++ .../cel/libraries/parse/parselib.go | 9 ++ .../cel/libraries/parse/parsing_test.go | 96 +++++++++++++++++++ 5 files changed, 146 insertions(+), 12 deletions(-) diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index 28201bde4..ab79d14d5 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -56,6 +56,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), @@ -128,7 +129,7 @@ func (c *CEL) getOrCreateProgram(expression string) (cel.Program, error) { return program, nil } -func (c *CEL) createEvalContext(event *events.EnrichedEvent) map[string]any { +func (c *CEL) CreateEvalContext(event *events.EnrichedEvent) map[string]any { eventType := event.Event.GetEventType() // Apply event converter if one is registered, otherwise cast to CelEvent @@ -175,15 +176,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 map[string]any, 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 +200,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 map[string]any, expression string) (string, error) { out, err := c.evaluateProgramWithContext(expression, evalContext) if err != nil { return "", err @@ -226,6 +218,24 @@ 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) + eventType := event.Event.GetEventType() + // Filter expressions to match event type for backward compatibility + var filtered []typesv1.RuleExpression + for _, expr := range expressions { + if expr.EventType == eventType { + filtered = append(filtered, expr) + } + } + return c.EvaluateRuleWithContext(evalContext, filtered) +} + +func (c *CEL) EvaluateExpression(event *events.EnrichedEvent, expression string) (string, error) { + evalContext := c.CreateEvalContext(event) + 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..31ea7897c 100644 --- a/pkg/rulemanager/cel/cel_interface.go +++ b/pkg/rulemanager/cel/cel_interface.go @@ -13,4 +13,9 @@ type RuleEvaluator interface { RegisterHelper(function cel.EnvOption) error RegisterCustomType(eventType utils.EventType, obj interface{}) error RegisterEventConverter(eventType utils.EventType, converter func(utils.K8sEvent) utils.K8sEvent) + + // Context-aware variants — create the eval context once and reuse across multiple evaluations + CreateEvalContext(event *events.EnrichedEvent) map[string]any + EvaluateRuleWithContext(evalContext map[string]any, expressions []typesv1.RuleExpression) (bool, error) + EvaluateExpressionWithContext(evalContext map[string]any, expression string) (string, error) } 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..b15a76ec2 100644 --- a/pkg/rulemanager/cel/libraries/parse/parsing_test.go +++ b/pkg/rulemanager/cel/libraries/parse/parsing_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/google/cel-go/cel" + "github.com/google/cel-go/ext" "github.com/kubescape/node-agent/pkg/config" "github.com/stretchr/testify/assert" ) @@ -42,6 +43,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 { @@ -76,6 +97,81 @@ func TestParseLibrary(t *testing.T) { } } +func TestCelBindWithJoinedArgs(t *testing.T) { + env, err := cel.NewEnv( + cel.Variable("event", cel.AnyType), + Parse(config.Config{}), + ext.Strings(), + ext.Bindings(), + ) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + + tests := []struct { + name string + expr string + args []string + expected bool + }{ + { + name: "cel.bind caches joined args - match", + expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('-e') && joined_args.contains('cmd'))", + args: []string{"nc", "-e", "/bin/sh", "cmd"}, + expected: true, + }, + { + name: "cel.bind caches joined args - no match", + expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('-e') && joined_args.contains('cmd'))", + args: []string{"ls", "-la"}, + expected: false, + }, + { + name: "cel.bind with multiple contains checks", + expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('socket') || joined_args.contains('exec') || joined_args.contains('pty'))", + args: []string{"python", "-c", "import pty; pty.spawn('/bin/sh')"}, + expected: true, + }, + { + name: "cel.bind with empty args", + expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('test'))", + args: []string{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ast, issues := env.Compile(tt.expr) + if issues != nil { + t.Fatalf("failed to compile expression: %v", issues.Err()) + } + + program, err := env.Program(ast) + if err != nil { + t.Fatalf("failed to create program: %v", err) + } + + result, _, err := program.Eval(map[string]interface{}{ + "event": map[string]interface{}{ + "args": tt.args, + "comm": "test", + }, + }) + if err != nil { + t.Fatalf("failed to eval program: %v", err) + } + + actual, ok := result.Value().(bool) + if !ok { + t.Fatalf("expected bool result, got %T", result.Value()) + } + + assert.Equal(t, tt.expected, actual) + }) + } +} + func TestParseLibraryErrorCases(t *testing.T) { env, err := cel.NewEnv( cel.Variable("event", cel.AnyType), From 8129fc1beca629e3dcd1b0c88c36b4d409bee164 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Thu, 12 Feb 2026 17:04:32 +0200 Subject: [PATCH 03/12] pre-index rules by event type to eliminate per-event scanning Signed-off-by: Yakir Oren --- pkg/rulemanager/rule_manager.go | 62 +++++++++----------------- pkg/rulemanager/rulecreator/factory.go | 8 +++- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/pkg/rulemanager/rule_manager.go b/pkg/rulemanager/rule_manager.go index 75241fcc2..72997319c 100644 --- a/pkg/rulemanager/rule_manager.go +++ b/pkg/rulemanager/rule_manager.go @@ -189,9 +189,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 +199,19 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) return } - eventType := enrichedEvent.Event.GetEventType() + evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent) + for _, rule := range rules { if !rule.Enabled { continue } + // Fast path: skip rules that have no expressions for this event type + ruleExpressions := rule.ExpressionsByEventType[eventType] + if len(ruleExpressions) == 0 { + continue + } + if !RuleAppliesToContext(&rule, enrichedEvent.SourceContext) { continue } @@ -216,17 +221,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 +238,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 @@ -338,19 +338,21 @@ func (rm *RuleManager) EvaluatePolicyRulesForEvent(eventType utils.EventType, ev creator := rm.ruleBindingCache.GetRuleCreator() rules := creator.CreateRulePolicyRulesByEventType(eventType) + enrichedEvent := &events.EnrichedEvent{Event: event} + evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent) + for _, rule := range rules { if !rule.SupportPolicy { continue } - enrichedEvent := &events.EnrichedEvent{Event: event} - ruleExpressions := rm.getRuleExpressions(rule, eventType) + ruleExpressions := rule.ExpressionsByEventType[eventType] if len(ruleExpressions) == 0 { 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 +384,13 @@ 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 map[string]any, 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,17 +400,6 @@ 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)) @@ -425,13 +407,13 @@ func hashStringToMD5(str string) string { return hashString } -func (rm *RuleManager) evaluateHTTPPayloadState(state map[string]any, enrichedEvent *events.EnrichedEvent) map[string]any { +func (rm *RuleManager) evaluateHTTPPayloadState(state map[string]any, evalContext map[string]any) 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 From 4d9a14267a3e0003ce76d312d417b2fcfe591334 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Thu, 12 Feb 2026 17:45:43 +0200 Subject: [PATCH 04/12] revert MakeHttpEvent and CreateEventFromRequest optimization Signed-off-by: Yakir Oren --- pkg/containerwatcher/v2/tracers/httpparse.go | 23 +++++++++++++++----- pkg/utils/datasource_event.go | 3 ++- pkg/utils/events.go | 6 ----- pkg/utils/struct_event.go | 10 +++++---- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/pkg/containerwatcher/v2/tracers/httpparse.go b/pkg/containerwatcher/v2/tracers/httpparse.go index 398a1b876..d1c011cb4 100644 --- a/pkg/containerwatcher/v2/tracers/httpparse.go +++ b/pkg/containerwatcher/v2/tracers/httpparse.go @@ -15,6 +15,20 @@ import ( "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" ) +var writeSyscalls = map[string]bool{ + "write": true, + "writev": true, + "sendto": true, + "sendmsg": true, +} + +var readSyscalls = map[string]bool{ + "read": true, + "readv": true, + "recvfrom": true, + "recvmsg": true, +} + var ConsistentHeaders = []string{ "Accept-Encoding", "Accept-Language", @@ -29,7 +43,7 @@ func CreateEventFromRequest(bpfEvent utils.HttpRawEvent) (utils.HttpEvent, error return nil, err } - request, err := ParseHttpRequest(FromCString(bpfEvent.GetBuf())) + direction, err := GetPacketDirection(bpfEvent.GetSyscall()) if err != nil { return nil, err } @@ -55,12 +69,11 @@ func ExtractConsistentHeaders(headers http.Header) map[string][]string { } func GetPacketDirection(syscall string) (consts.NetworkDirection, error) { - switch syscall { - case "read", "readv", "recvfrom", "recvmsg": + if readSyscalls[syscall] { return consts.Inbound, nil - case "write", "writev", "sendto", "sendmsg": + } else if writeSyscalls[syscall] { return consts.Outbound, nil - default: + } else { return "", fmt.Errorf("unknown syscall %s", syscall) } } diff --git a/pkg/utils/datasource_event.go b/pkg/utils/datasource_event.go index eac24a878..55abaca20 100644 --- a/pkg/utils/datasource_event.go +++ b/pkg/utils/datasource_event.go @@ -2,6 +2,7 @@ package utils import ( "errors" + "net" "net/http" "os" "strings" @@ -760,7 +761,7 @@ func (e *DatasourceEvent) MakeHttpEvent(request *http.Request, direction consts. Datasource: e.Datasource, Direction: direction, EventType: e.EventType, - Internal: isPrivateIP(e.GetOtherIp()), + Internal: func() bool { ip := net.ParseIP(e.GetOtherIp()); return ip != nil && ip.IsPrivate() }(), Request: request, Response: e.Response, Syscall: e.Syscall, diff --git a/pkg/utils/events.go b/pkg/utils/events.go index 8d286ffe0..fda2e5a66 100644 --- a/pkg/utils/events.go +++ b/pkg/utils/events.go @@ -2,7 +2,6 @@ package utils import ( "fmt" - "net" "net/http" "path/filepath" @@ -10,11 +9,6 @@ import ( "github.com/kubescape/storage/pkg/apis/softwarecomposition/consts" ) -func isPrivateIP(ipStr string) bool { - ip := net.ParseIP(ipStr) - return ip != nil && ip.IsPrivate() -} - type HTTPDataType int const ( diff --git a/pkg/utils/struct_event.go b/pkg/utils/struct_event.go index 8c39376fd..278c8ebe8 100644 --- a/pkg/utils/struct_event.go +++ b/pkg/utils/struct_event.go @@ -1,6 +1,7 @@ package utils import ( + "net" "net/http" "time" @@ -432,10 +433,11 @@ func (e *StructEvent) IsDir() bool { } func (e *StructEvent) MakeHttpEvent(request *http.Request, direction consts.NetworkDirection) HttpEvent { - e.Request = request - e.Direction = direction - e.Internal = isPrivateIP(e.GetOtherIp()) - return e + event := *e + event.Request = request + event.Direction = direction + event.Internal = func() bool { ip := net.ParseIP(e.GetOtherIp()); return ip != nil && ip.IsPrivate() }() + return &event } func (e *StructEvent) Release() {} From bc5ae261051b3ea0d860cfcb37f5b0b3d84e5887 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Thu, 12 Feb 2026 18:35:17 +0200 Subject: [PATCH 05/12] remove cel.bind test for external library Signed-off-by: Yakir Oren --- .../cel/libraries/parse/parsing_test.go | 76 ------------------- 1 file changed, 76 deletions(-) diff --git a/pkg/rulemanager/cel/libraries/parse/parsing_test.go b/pkg/rulemanager/cel/libraries/parse/parsing_test.go index b15a76ec2..ba07fac95 100644 --- a/pkg/rulemanager/cel/libraries/parse/parsing_test.go +++ b/pkg/rulemanager/cel/libraries/parse/parsing_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/google/cel-go/cel" - "github.com/google/cel-go/ext" "github.com/kubescape/node-agent/pkg/config" "github.com/stretchr/testify/assert" ) @@ -97,81 +96,6 @@ func TestParseLibrary(t *testing.T) { } } -func TestCelBindWithJoinedArgs(t *testing.T) { - env, err := cel.NewEnv( - cel.Variable("event", cel.AnyType), - Parse(config.Config{}), - ext.Strings(), - ext.Bindings(), - ) - if err != nil { - t.Fatalf("failed to create env: %v", err) - } - - tests := []struct { - name string - expr string - args []string - expected bool - }{ - { - name: "cel.bind caches joined args - match", - expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('-e') && joined_args.contains('cmd'))", - args: []string{"nc", "-e", "/bin/sh", "cmd"}, - expected: true, - }, - { - name: "cel.bind caches joined args - no match", - expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('-e') && joined_args.contains('cmd'))", - args: []string{"ls", "-la"}, - expected: false, - }, - { - name: "cel.bind with multiple contains checks", - expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('socket') || joined_args.contains('exec') || joined_args.contains('pty'))", - args: []string{"python", "-c", "import pty; pty.spawn('/bin/sh')"}, - expected: true, - }, - { - name: "cel.bind with empty args", - expr: "cel.bind(joined_args, event.args.join(' '), joined_args.contains('test'))", - args: []string{}, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ast, issues := env.Compile(tt.expr) - if issues != nil { - t.Fatalf("failed to compile expression: %v", issues.Err()) - } - - program, err := env.Program(ast) - if err != nil { - t.Fatalf("failed to create program: %v", err) - } - - result, _, err := program.Eval(map[string]interface{}{ - "event": map[string]interface{}{ - "args": tt.args, - "comm": "test", - }, - }) - if err != nil { - t.Fatalf("failed to eval program: %v", err) - } - - actual, ok := result.Value().(bool) - if !ok { - t.Fatalf("expected bool result, got %T", result.Value()) - } - - assert.Equal(t, tt.expected, actual) - }) - } -} - func TestParseLibraryErrorCases(t *testing.T) { env, err := cel.NewEnv( cel.Variable("event", cel.AnyType), From ab07a9342e5d802141ade0388ac4aa8af905b1ab Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Thu, 12 Feb 2026 19:15:17 +0200 Subject: [PATCH 06/12] removed old functions and replace EnrichedEvent with utils.K8sEvent in CEL evaluation Signed-off-by: Yakir Oren --- pkg/rulemanager/cel/cel.go | 22 +++++++--------------- pkg/rulemanager/cel/cel_interface.go | 8 ++++++-- pkg/rulemanager/rule_manager.go | 9 +++------ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index ab79d14d5..2c9fb2230 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -129,15 +129,15 @@ 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) map[string]any { + eventType := event.GetEventType() // Apply event converter if one is registered, otherwise cast to CelEvent var obj interface{} if converter, exists := c.eventConverters[eventType]; exists { - obj, _ = xcel.NewObject(converter(event.Event)) + obj, _ = xcel.NewObject(converter(event)) } else { - obj, _ = xcel.NewObject(event.Event.(utils.CelEvent)) + obj, _ = xcel.NewObject(event.(utils.CelEvent)) } evalContext := map[string]any{ @@ -219,20 +219,12 @@ func (c *CEL) EvaluateExpressionWithContext(evalContext map[string]any, expressi } func (c *CEL) EvaluateRule(event *events.EnrichedEvent, expressions []typesv1.RuleExpression) (bool, error) { - evalContext := c.CreateEvalContext(event) - eventType := event.Event.GetEventType() - // Filter expressions to match event type for backward compatibility - var filtered []typesv1.RuleExpression - for _, expr := range expressions { - if expr.EventType == eventType { - filtered = append(filtered, expr) - } - } - return c.EvaluateRuleWithContext(evalContext, filtered) + evalContext := c.CreateEvalContext(event.Event) + return c.EvaluateRuleWithContext(evalContext, expressions) } func (c *CEL) EvaluateExpression(event *events.EnrichedEvent, expression string) (string, error) { - evalContext := c.CreateEvalContext(event) + evalContext := c.CreateEvalContext(event.Event) return c.EvaluateExpressionWithContext(evalContext, expression) } diff --git a/pkg/rulemanager/cel/cel_interface.go b/pkg/rulemanager/cel/cel_interface.go index 31ea7897c..b7353677c 100644 --- a/pkg/rulemanager/cel/cel_interface.go +++ b/pkg/rulemanager/cel/cel_interface.go @@ -8,14 +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) - // Context-aware variants — create the eval context once and reuse across multiple evaluations - CreateEvalContext(event *events.EnrichedEvent) map[string]any + CreateEvalContext(event utils.K8sEvent) map[string]any EvaluateRuleWithContext(evalContext map[string]any, expressions []typesv1.RuleExpression) (bool, error) EvaluateExpressionWithContext(evalContext map[string]any, expression string) (string, error) } diff --git a/pkg/rulemanager/rule_manager.go b/pkg/rulemanager/rule_manager.go index 72997319c..5b2daf366 100644 --- a/pkg/rulemanager/rule_manager.go +++ b/pkg/rulemanager/rule_manager.go @@ -199,7 +199,7 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) return } - evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent) + evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent.Event) for _, rule := range rules { if !rule.Enabled { @@ -333,13 +333,12 @@ 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) - enrichedEvent := &events.EnrichedEvent{Event: event} - evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent) + evalContext := rm.celEvaluator.CreateEvalContext(event) for _, rule := range rules { if !rule.SupportPolicy { @@ -384,7 +383,6 @@ func (rm *RuleManager) validateRulePolicy(rule typesv1.Rule, event utils.K8sEven return allowed } - func (rm *RuleManager) getUniqueIdAndMessage(evalContext map[string]any, rule typesv1.Rule) (string, string, error) { message, err := rm.celEvaluator.EvaluateExpressionWithContext(evalContext, rule.Expressions.Message) if err != nil { @@ -400,7 +398,6 @@ func (rm *RuleManager) getUniqueIdAndMessage(evalContext map[string]any, rule ty return message, uniqueID, err } - func hashStringToMD5(str string) string { hash := md5.Sum([]byte(str)) hashString := fmt.Sprintf("%x", hash) From 6e4278b3ecd31f518cce9ed2bf49ea302d44bfcf Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Tue, 24 Feb 2026 16:03:41 +0200 Subject: [PATCH 07/12] implement activation interface Signed-off-by: Yakir Oren --- pkg/rulemanager/cel/cel.go | 75 +++++++++++++++++++++------- pkg/rulemanager/cel/cel_interface.go | 6 +-- pkg/rulemanager/rule_manager.go | 6 ++- 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index 2c9fb2230..642f31a02 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -7,6 +7,7 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types/ref" "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 +24,48 @@ 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() { + 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 { @@ -129,33 +172,27 @@ func (c *CEL) getOrCreateProgram(expression string) (cel.Program, error) { return program, nil } -func (c *CEL) CreateEvalContext(event utils.K8sEvent) map[string]any { +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]) if converter, exists := c.eventConverters[eventType]; exists { - obj, _ = xcel.NewObject(converter(event)) + obj.Raw = converter(event).(utils.CelEvent) } else { - obj, _ = xcel.NewObject(event.(utils.CelEvent)) - } - - evalContext := map[string]any{ - "eventType": string(eventType), - "event": obj, + obj.Raw = event.(utils.CelEvent) } - // For HTTP events, also add "http" variable - if eventType == utils.HTTPEventType { - evalContext["http"] = obj - } + activation := eventActivationPool.Get().(*EventActivation) + activation.eventType = string(eventType) + activation.event = obj + activation.isHTTP = eventType == utils.HTTPEventType - return evalContext + 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 @@ -176,7 +213,7 @@ func (c *CEL) evaluateProgramWithContext(expression string, evalContext map[stri return out, nil } -func (c *CEL) EvaluateRuleWithContext(evalContext map[string]any, expressions []typesv1.RuleExpression) (bool, error) { +func (c *CEL) EvaluateRuleWithContext(evalContext *EventActivation, expressions []typesv1.RuleExpression) (bool, error) { for _, expression := range expressions { out, err := c.evaluateProgramWithContext(expression.Expression, evalContext) if err != nil { @@ -200,7 +237,7 @@ func (c *CEL) EvaluateRuleWithContext(evalContext map[string]any, expressions [] return true, nil } -func (c *CEL) EvaluateExpressionWithContext(evalContext map[string]any, expression string) (string, error) { +func (c *CEL) EvaluateExpressionWithContext(evalContext *EventActivation, expression string) (string, error) { out, err := c.evaluateProgramWithContext(expression, evalContext) if err != nil { return "", err @@ -220,11 +257,13 @@ func (c *CEL) EvaluateExpressionWithContext(evalContext map[string]any, expressi 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) } diff --git a/pkg/rulemanager/cel/cel_interface.go b/pkg/rulemanager/cel/cel_interface.go index b7353677c..070a3a178 100644 --- a/pkg/rulemanager/cel/cel_interface.go +++ b/pkg/rulemanager/cel/cel_interface.go @@ -19,7 +19,7 @@ type RuleEvaluator interface { RegisterCustomType(eventType utils.EventType, obj interface{}) error RegisterEventConverter(eventType utils.EventType, converter func(utils.K8sEvent) utils.K8sEvent) - CreateEvalContext(event utils.K8sEvent) map[string]any - EvaluateRuleWithContext(evalContext map[string]any, expressions []typesv1.RuleExpression) (bool, error) - EvaluateExpressionWithContext(evalContext map[string]any, expression string) (string, error) + 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/rule_manager.go b/pkg/rulemanager/rule_manager.go index 5b2daf366..12a7b1430 100644 --- a/pkg/rulemanager/rule_manager.go +++ b/pkg/rulemanager/rule_manager.go @@ -200,6 +200,7 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) } evalContext := rm.celEvaluator.CreateEvalContext(enrichedEvent.Event) + defer evalContext.Release() for _, rule := range rules { if !rule.Enabled { @@ -339,6 +340,7 @@ func (rm *RuleManager) EvaluatePolicyRulesForEvent(eventType utils.EventType, ev rules := creator.CreateRulePolicyRulesByEventType(eventType) evalContext := rm.celEvaluator.CreateEvalContext(event) + defer evalContext.Release() for _, rule := range rules { if !rule.SupportPolicy { @@ -383,7 +385,7 @@ func (rm *RuleManager) validateRulePolicy(rule typesv1.Rule, event utils.K8sEven return allowed } -func (rm *RuleManager) getUniqueIdAndMessage(evalContext map[string]any, rule typesv1.Rule) (string, string, error) { +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)) @@ -404,7 +406,7 @@ func hashStringToMD5(str string) string { return hashString } -func (rm *RuleManager) evaluateHTTPPayloadState(state map[string]any, evalContext map[string]any) 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 From 43ff3a836a010fc995242189c967d14a662fb7fb Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Mon, 2 Mar 2026 13:45:14 +0200 Subject: [PATCH 08/12] optimize CEL field boxing and minor allocations Signed-off-by: Yakir Oren --- go.mod | 2 +- go.sum | 4 +- pkg/rulemanager/cel/cel.go | 28 ++- pkg/rulemanager/rule_manager.go | 16 +- pkg/utils/cel.go | 297 +++++++++++++++++++------------- 5 files changed, 208 insertions(+), 139 deletions(-) diff --git a/go.mod b/go.mod index d5a5b412b..dd7bf6877 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d81485b14..5315bdeaa 100644 --- a/go.sum +++ b/go.sum @@ -1729,8 +1729,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= diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index 642f31a02..01d40f4d8 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -6,6 +6,7 @@ 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" @@ -58,6 +59,10 @@ 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 @@ -85,11 +90,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{ @@ -176,10 +181,19 @@ func (c *CEL) CreateEvalContext(event utils.K8sEvent) *EventActivation { eventType := event.GetEventType() obj := objectPool.Get().(*xcel.Object[utils.CelEvent]) + var celEvent utils.CelEvent if converter, exists := c.eventConverters[eventType]; exists { - obj.Raw = converter(event).(utils.CelEvent) + celEvent = converter(event).(utils.CelEvent) + } else { + celEvent = event.(utils.CelEvent) + } + + if eventType == utils.HTTPEventType { + wrapper := utils.HttpEventWrapperPool.Get().(*utils.HttpEventWrapper) + wrapper.CelEvent = celEvent + obj.Raw = wrapper } else { - obj.Raw = event.(utils.CelEvent) + obj.Raw = celEvent } activation := eventActivationPool.Get().(*EventActivation) diff --git a/pkg/rulemanager/rule_manager.go b/pkg/rulemanager/rule_manager.go index 12a7b1430..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" @@ -208,8 +209,8 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) } // Fast path: skip rules that have no expressions for this event type - ruleExpressions := rule.ExpressionsByEventType[eventType] - if len(ruleExpressions) == 0 { + ruleExpressions, ok := rule.ExpressionsByEventType[eventType] + if !ok { continue } @@ -283,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()))) } } @@ -347,8 +348,8 @@ func (rm *RuleManager) EvaluatePolicyRulesForEvent(eventType utils.EventType, ev continue } - ruleExpressions := rule.ExpressionsByEventType[eventType] - if len(ruleExpressions) == 0 { + ruleExpressions, ok := rule.ExpressionsByEventType[eventType] + if !ok { continue } @@ -402,8 +403,7 @@ func (rm *RuleManager) getUniqueIdAndMessage(evalContext *cel.EventActivation, r 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, evalContext *cel.EventActivation) map[string]any { 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 }), }, } From 116f43ec6fa0d663c382a8fc173051a4f3b484cd Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Mon, 2 Mar 2026 14:37:07 +0200 Subject: [PATCH 09/12] add CEL static optimizer for AST-level constant folding Signed-off-by: Yakir Oren --- go.mod | 6 +-- go.sum | 6 +++ pkg/rulemanager/cel/cel.go | 18 +++++++ pkg/rulemanager/cel/cel_test.go | 93 +++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 pkg/rulemanager/cel/cel_test.go diff --git a/go.mod b/go.mod index dd7bf6877..8f70b87a2 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 @@ -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 diff --git a/go.sum b/go.sum index 5315bdeaa..828e76dd4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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= @@ -743,6 +745,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW 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= @@ -1235,6 +1239,8 @@ 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= diff --git a/pkg/rulemanager/cel/cel.go b/pkg/rulemanager/cel/cel.go index 01d40f4d8..a9c88bb58 100644 --- a/pkg/rulemanager/cel/cel.go +++ b/pkg/rulemanager/cel/cel.go @@ -82,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) { @@ -117,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, @@ -124,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 @@ -146,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 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) + }) + } +} From 475d86d1abe77df8ad3a5ef688805c1925999a32 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Tue, 3 Mar 2026 11:29:27 +0200 Subject: [PATCH 10/12] add sync pool for bufio reader Signed-off-by: Yakir Oren --- go.mod | 3 +-- go.sum | 9 +-------- pkg/containerwatcher/v2/tracers/httpparse.go | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 8f70b87a2..7a60f839e 100644 --- a/go.mod +++ b/go.mod @@ -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 828e76dd4..a3f0ccd97 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -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= @@ -743,8 +741,6 @@ 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= @@ -1237,8 +1233,6 @@ 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= @@ -1538,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= @@ -1907,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) } From 9eac12ab79d1f6910c8f95daec69184b30c1b196 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Mon, 9 Mar 2026 14:04:09 +0200 Subject: [PATCH 11/12] add MetricsNoop for zero-cost disabled metrics path Signed-off-by: Yakir Oren --- cmd/main.go | 2 +- pkg/metricsmanager/metrics_manager_noop.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 pkg/metricsmanager/metrics_manager_noop.go 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/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 From 94494ecda23370b7a92fbe56f2135471680836b5 Mon Sep 17 00:00:00 2001 From: Yakir Oren Date: Mon, 9 Mar 2026 14:42:44 +0200 Subject: [PATCH 12/12] add ExpressionsByEventType field and Init method to Rule struct --- pkg/rulemanager/types/v1/types.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 {