Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions pkg/rulemanager/rule_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,7 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent)
if enrichedEvent.SourceContext == nil || enrichedEvent.SourceContext.Context() == contextdetection.Kubernetes {
rules = rm.ruleBindingCache.ListRulesForPod(namespace, pod)
} else {
// TODO: rule filtering based on context
rules = rm.ruleBindingCache.GetRuleCreator().CreateAllRules()
rules = rm.ruleBindingCache.GetRuleCreator().CreateRulesForContext(enrichedEvent.SourceContext.Context())
}

if len(rules) == 0 {
Expand Down
35 changes: 35 additions & 0 deletions pkg/rulemanager/rulecreator/context_match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package rulecreator

import (
"strings"

"github.com/kubescape/node-agent/pkg/contextdetection"
typesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1"
)

// RuleMatchesContext is the single source of truth for context-based rule matching.
func RuleMatchesContext(rule *typesv1.Rule, currentContext contextdetection.EventSourceContext) bool {
isContainerContext := currentContext == contextdetection.Kubernetes ||
currentContext == contextdetection.Standalone ||
currentContext == contextdetection.Container ||
currentContext == contextdetection.ECS

var hasContextTags bool
for _, tag := range rule.Tags {
if ctx, found := strings.CutPrefix(tag, "context:"); found {
if ctx == string(currentContext) {
return true
}
if ctx == string(contextdetection.Container) && isContainerContext {
return true
}
hasContextTags = true
}
}

if !hasContextTags {
return currentContext == contextdetection.Kubernetes
}

return false
}
157 changes: 157 additions & 0 deletions pkg/rulemanager/rulecreator/context_match_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package rulecreator

import (
"testing"

"github.com/kubescape/node-agent/pkg/contextdetection"
typesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1"
)

func TestRuleMatchesContext(t *testing.T) {
tests := []struct {
name string
rule typesv1.Rule
context contextdetection.EventSourceContext
expected bool
}{
{
name: "host tag matches host context",
rule: typesv1.Rule{Tags: []string{"context:host"}},
context: contextdetection.Host,
expected: true,
},
{
name: "host tag does not match kubernetes context",
rule: typesv1.Rule{Tags: []string{"context:host"}},
context: contextdetection.Kubernetes,
expected: false,
},
{
name: "kubernetes tag matches kubernetes context",
rule: typesv1.Rule{Tags: []string{"context:kubernetes"}},
context: contextdetection.Kubernetes,
expected: true,
},
{
name: "kubernetes tag does not match host context",
rule: typesv1.Rule{Tags: []string{"context:kubernetes"}},
context: contextdetection.Host,
expected: false,
},
{
name: "standalone tag matches standalone context",
rule: typesv1.Rule{Tags: []string{"context:standalone"}},
context: contextdetection.Standalone,
expected: true,
},
{
name: "ecs tag matches ecs context",
rule: typesv1.Rule{Tags: []string{"context:ecs"}},
context: contextdetection.ECS,
expected: true,
},
{
name: "container tag matches kubernetes context",
rule: typesv1.Rule{Tags: []string{"context:container"}},
context: contextdetection.Kubernetes,
expected: true,
},
{
name: "container tag matches standalone context",
rule: typesv1.Rule{Tags: []string{"context:container"}},
context: contextdetection.Standalone,
expected: true,
},
{
name: "container tag matches ecs context",
rule: typesv1.Rule{Tags: []string{"context:container"}},
context: contextdetection.ECS,
expected: true,
},
{
name: "container tag matches container context",
rule: typesv1.Rule{Tags: []string{"context:container"}},
context: contextdetection.Container,
expected: true,
},
{
name: "container tag does not match host context",
rule: typesv1.Rule{Tags: []string{"context:container"}},
context: contextdetection.Host,
expected: false,
},

{
name: "no context tags defaults to kubernetes",
rule: typesv1.Rule{Tags: []string{"some-other-tag"}},
context: contextdetection.Kubernetes,
expected: true,
},
{
name: "no context tags rejects host",
rule: typesv1.Rule{Tags: []string{"some-other-tag"}},
context: contextdetection.Host,
expected: false,
},
{
name: "no context tags rejects standalone",
rule: typesv1.Rule{Tags: []string{}},
context: contextdetection.Standalone,
expected: false,
},
{
name: "nil tags defaults to kubernetes",
rule: typesv1.Rule{},
context: contextdetection.Kubernetes,
expected: true,
},
{
name: "nil tags rejects host",
rule: typesv1.Rule{},
context: contextdetection.Host,
expected: false,
},

{
name: "multiple context tags: host+kubernetes matches host",
rule: typesv1.Rule{Tags: []string{"context:host", "context:kubernetes"}},
context: contextdetection.Host,
expected: true,
},
{
name: "multiple context tags: host+kubernetes matches kubernetes",
rule: typesv1.Rule{Tags: []string{"context:host", "context:kubernetes"}},
context: contextdetection.Kubernetes,
expected: true,
},
{
name: "multiple context tags: host+kubernetes rejects standalone",
rule: typesv1.Rule{Tags: []string{"context:host", "context:kubernetes"}},
context: contextdetection.Standalone,
expected: false,
},

{
name: "mixed tags with context:host matches host",
rule: typesv1.Rule{Tags: []string{"severity:high", "context:host", "category:network"}},
context: contextdetection.Host,
expected: true,
},
{
name: "mixed tags with context:host rejects kubernetes",
rule: typesv1.Rule{Tags: []string{"severity:high", "context:host", "category:network"}},
context: contextdetection.Kubernetes,
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := RuleMatchesContext(&tt.rule, tt.context)
if result != tt.expected {
t.Errorf("RuleMatchesContext() = %v, want %v (rule tags: %v, context: %s)",
result, tt.expected, tt.rule.Tags, tt.context)
}
})
}
}
21 changes: 21 additions & 0 deletions pkg/rulemanager/rulecreator/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"slices"
"sync"

"github.com/kubescape/node-agent/pkg/contextdetection"
"github.com/kubescape/node-agent/pkg/rulemanager/prefilter"
typesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1"
"github.com/kubescape/node-agent/pkg/utils"
Expand Down Expand Up @@ -91,6 +92,9 @@ func (r *RuleCreatorImpl) GetAllRuleIDs() []string {
}

func (r *RuleCreatorImpl) CreateAllRules() []typesv1.Rule {
r.mutex.Lock()
defer r.mutex.Unlock()

var rules []typesv1.Rule
for i := range r.Rules {
if r.Rules[i].Prefilter == nil {
Expand All @@ -101,6 +105,23 @@ func (r *RuleCreatorImpl) CreateAllRules() []typesv1.Rule {
return rules
}

func (r *RuleCreatorImpl) CreateRulesForContext(ctx contextdetection.EventSourceContext) []typesv1.Rule {
r.mutex.Lock()
defer r.mutex.Unlock()

var rules []typesv1.Rule
for i := range r.Rules {
if !RuleMatchesContext(&r.Rules[i], ctx) {
continue
}
if r.Rules[i].Prefilter == nil {
r.Rules[i].Prefilter = prefilter.ParseWithDefaults(r.Rules[i].State, nil)
}
rules = append(rules, r.Rules[i])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return rules
}

// SyncRules replaces the current rules with the new set of rules
// It removes rules that are no longer present and adds/updates existing ones
func (r *RuleCreatorImpl) SyncRules(newRules []typesv1.Rule) {
Expand Down
Loading
Loading