Skip to content
Open
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
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func main() {
if cfg.EnablePrometheusExporter {
prometheusExporter = metricprometheus.NewPrometheusMetric()
} else {
prometheusExporter = metricsmanager.NewMetricsMock()
prometheusExporter = metricsmanager.NewMetricsNoop()
}

// Create watchers
Expand Down
21 changes: 4 additions & 17 deletions pkg/containerwatcher/v2/tracers/httpparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/metricsmanager/metrics_manager_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type MetricsManager interface {
ReportEvent(eventType utils.EventType)
ReportFailedEvent()
ReportRuleProcessed(ruleID string)
ReportRulePrefiltered(ruleName string)
ReportRuleAlert(ruleID string)
ReportRuleEvaluationTime(ruleID string, eventType utils.EventType, duration time.Duration)
//ReportEbpfStats(stats *top.Event[toptypes.Stats])
Expand Down
2 changes: 2 additions & 0 deletions pkg/metricsmanager/metrics_manager_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func (m *MetricsMock) ReportRuleEvaluationTime(ruleID string, eventType utils.Ev
//func (m *MetricsMock) ReportEbpfStats(stats *top.Event[toptypes.Stats]) {
//}

func (m *MetricsMock) ReportRulePrefiltered(ruleName string) {}

func (m *MetricsMock) ReportContainerStart() {}

func (m *MetricsMock) ReportContainerStop() {}
23 changes: 23 additions & 0 deletions pkg/metricsmanager/metrics_manager_noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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) ReportRulePrefiltered(_ string) {}
func (m *MetricsNoop) ReportRuleAlert(_ string) {}
func (m *MetricsNoop) ReportRuleEvaluationTime(_ string, _ utils.EventType, _ time.Duration) {}
func (m *MetricsNoop) ReportContainerStart() {}
func (m *MetricsNoop) ReportContainerStop() {}
45 changes: 39 additions & 6 deletions pkg/metricsmanager/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ type PrometheusMetric struct {
ebpfKmodCounter prometheus.Counter
ebpfUnshareCounter prometheus.Counter
ebpfBpfCounter prometheus.Counter
ruleCounter *prometheus.CounterVec
alertCounter *prometheus.CounterVec
ruleCounter *prometheus.CounterVec
rulePrefilteredCounter *prometheus.CounterVec
alertCounter *prometheus.CounterVec
ruleEvaluationTime *prometheus.HistogramVec

// Program ID metrics
Expand All @@ -60,8 +61,9 @@ type PrometheusMetric struct {
containerStopCounter prometheus.Counter

// Cache to avoid allocating Labels maps on every call
ruleCounterCache map[string]prometheus.Counter
alertCounterCache map[string]prometheus.Counter
ruleCounterCache map[string]prometheus.Counter
rulePrefilteredCounterCache map[string]prometheus.Counter
alertCounterCache map[string]prometheus.Counter
counterCacheMutex sync.RWMutex
}

Expand Down Expand Up @@ -139,6 +141,10 @@ func NewPrometheusMetric() *PrometheusMetric {
Name: "node_agent_rule_counter",
Help: "The total number of rules processed by the engine",
}, []string{prometheusRuleIdLabel}),
rulePrefilteredCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "node_agent_rule_prefiltered_total",
Help: "Total number of rule evaluations skipped by pre-filter",
}, []string{prometheusRuleIdLabel}),
alertCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "node_agent_alert_counter",
Help: "The total number of alerts sent by the engine",
Expand Down Expand Up @@ -201,8 +207,9 @@ func NewPrometheusMetric() *PrometheusMetric {
}),

// Initialize counter caches
ruleCounterCache: make(map[string]prometheus.Counter),
alertCounterCache: make(map[string]prometheus.Counter),
ruleCounterCache: make(map[string]prometheus.Counter),
rulePrefilteredCounterCache: make(map[string]prometheus.Counter),
alertCounterCache: make(map[string]prometheus.Counter),
}
}

Expand All @@ -225,6 +232,7 @@ func (p *PrometheusMetric) Destroy() {
prometheus.Unregister(p.ebpfRandomXCounter)
prometheus.Unregister(p.ebpfFailedCounter)
prometheus.Unregister(p.ruleCounter)
prometheus.Unregister(p.rulePrefilteredCounter)
prometheus.Unregister(p.alertCounter)
prometheus.Unregister(p.ruleEvaluationTime)
prometheus.Unregister(p.ebpfSymlinkCounter)
Expand Down Expand Up @@ -342,6 +350,31 @@ func (p *PrometheusMetric) ReportRuleProcessed(ruleID string) {
p.getCachedRuleCounter(ruleID).Inc()
}

func (p *PrometheusMetric) getCachedRulePrefilteredCounter(ruleName string) prometheus.Counter {
p.counterCacheMutex.RLock()
counter, exists := p.rulePrefilteredCounterCache[ruleName]
p.counterCacheMutex.RUnlock()

if exists {
return counter
}

p.counterCacheMutex.Lock()
defer p.counterCacheMutex.Unlock()

if counter, exists := p.rulePrefilteredCounterCache[ruleName]; exists {
return counter
}

counter = p.rulePrefilteredCounter.With(prometheus.Labels{prometheusRuleIdLabel: ruleName})
p.rulePrefilteredCounterCache[ruleName] = counter
return counter
}

func (p *PrometheusMetric) ReportRulePrefiltered(ruleName string) {
p.getCachedRulePrefilteredCounter(ruleName).Inc()
}

func (p *PrometheusMetric) ReportRuleAlert(ruleID string) {
p.getCachedAlertCounter(ruleID).Inc()
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/rulebindingmanager/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/kubescape/node-agent/pkg/k8sclient"
"github.com/kubescape/node-agent/pkg/rulebindingmanager"
typesv1 "github.com/kubescape/node-agent/pkg/rulebindingmanager/types/v1"
"github.com/kubescape/node-agent/pkg/rulemanager/prefilter"
"github.com/kubescape/node-agent/pkg/rulemanager/rulecreator"
rulemanagertypesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1"
"github.com/kubescape/node-agent/pkg/utils"
Expand Down Expand Up @@ -400,14 +401,19 @@ func (c *RBCache) createRules(rulesForPod []typesv1.RuntimeAlertRuleBindingRule)
func (c *RBCache) createRule(r *typesv1.RuntimeAlertRuleBindingRule) []rulemanagertypesv1.Rule {
if r.RuleID != "" {
rule := c.ruleCreator.CreateRuleByID(r.RuleID)
rule.Prefilter = prefilter.ParseWithDefaults(rule.State, r.Parameters)
return []rulemanagertypesv1.Rule{rule}
}
if r.RuleName != "" {
rule := c.ruleCreator.CreateRuleByName(r.RuleName)
rule.Prefilter = prefilter.ParseWithDefaults(rule.State, r.Parameters)
return []rulemanagertypesv1.Rule{rule}
}
if len(r.RuleTags) > 0 {
rules := c.ruleCreator.CreateRulesByTags(r.RuleTags)
for i := range rules {
rules[i].Prefilter = prefilter.ParseWithDefaults(rules[i].State, r.Parameters)
}
return rules
}

Expand Down
44 changes: 44 additions & 0 deletions pkg/rulebindingmanager/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
typesv1 "github.com/kubescape/node-agent/pkg/rulebindingmanager/types/v1"
rulemanagertypesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -1040,3 +1041,46 @@ func TestDiff(t *testing.T) {
})
}
}

func TestCreateRulePrefilter(t *testing.T) {
tests := []struct {
name string
binding *typesv1.RuntimeAlertRuleBindingRule
wantNil bool
wantIgnore []string
wantIncl []string
}{
{
name: "parameters propagate to prefilter",
binding: &typesv1.RuntimeAlertRuleBindingRule{
RuleID: "R0002",
Parameters: map[string]interface{}{
"ignorePrefixes": []interface{}{"/tmp", "/var/log"},
"includePrefixes": []interface{}{"/etc"},
},
},
wantIgnore: []string{"/tmp/", "/var/log/"},
wantIncl: []string{"/etc/"},
},
{
name: "nil parameters produce nil prefilter",
binding: &typesv1.RuntimeAlertRuleBindingRule{RuleID: "R0002"},
wantNil: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewCacheMock("")
rules := c.createRule(tt.binding)
require.Len(t, rules, 1)
if tt.wantNil {
assert.Nil(t, rules[0].Prefilter)
} else {
require.NotNil(t, rules[0].Prefilter)
assert.Equal(t, tt.wantIgnore, rules[0].Prefilter.IgnorePrefixes)
assert.Equal(t, tt.wantIncl, rules[0].Prefilter.IncludePrefixes)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ spec:
rules:
- ruleName: "Malicious SSH Connection"
parameters:
allowedPorts: [22, 2222]
ports: [22, 2222]
67 changes: 67 additions & 0 deletions pkg/rulemanager/extract_event_fields_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package rulemanager

import (
"net/http"
"testing"

"github.com/kubescape/node-agent/pkg/rulemanager/prefilter"
"github.com/kubescape/node-agent/pkg/utils"
"github.com/kubescape/storage/pkg/apis/softwarecomposition/consts"
"github.com/stretchr/testify/assert"
)

func TestExtractEventFields(t *testing.T) {
tests := []struct {
name string
event *utils.StructEvent
expect prefilter.EventFields
}{
{
name: "open event extracts path",
event: &utils.StructEvent{EventType: utils.OpenEventType, Path: "/etc/passwd"},
expect: prefilter.EventFields{Path: "/etc/passwd", Extracted: true},
},
{
name: "exec event extracts exe path",
event: &utils.StructEvent{EventType: utils.ExecveEventType, ExePath: "/usr/bin/curl"},
expect: prefilter.EventFields{Path: "/usr/bin/curl", Extracted: true},
},
{
name: "HTTP event extracts direction, method, port",
event: &utils.StructEvent{EventType: utils.HTTPEventType, Direction: consts.Inbound, DstPort: 8080, Request: &http.Request{Method: "POST"}},
expect: prefilter.EventFields{Dir: prefilter.DirInbound, MethodBit: prefilter.MethodPOST, DstPort: 8080, Extracted: true},
},
{
name: "HTTP nil request leaves method zero",
event: &utils.StructEvent{EventType: utils.HTTPEventType, Direction: consts.Outbound},
expect: prefilter.EventFields{Dir: prefilter.DirOutbound, Extracted: true},
},
{
name: "HTTP unknown direction maps to DirNone",
event: &utils.StructEvent{EventType: utils.HTTPEventType, Direction: "unknown", Request: &http.Request{Method: "POST"}},
expect: prefilter.EventFields{MethodBit: prefilter.MethodPOST, Extracted: true},
},
{
name: "network event extracts port and sets PortEligible",
event: &utils.StructEvent{EventType: utils.NetworkEventType, DstPort: 443},
expect: prefilter.EventFields{DstPort: 443, PortEligible: true, Extracted: true},
},
{
name: "SSH event extracts port and sets PortEligible",
event: &utils.StructEvent{EventType: utils.SSHEventType, DstPort: 22},
expect: prefilter.EventFields{DstPort: 22, PortEligible: true, Extracted: true},
},
{
name: "unhandled event type returns empty fields",
event: &utils.StructEvent{EventType: utils.DnsEventType},
expect: prefilter.EventFields{Extracted: true},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractEventFields(tt.event)
assert.Equal(t, tt.expect, got)
})
}
}
Loading
Loading