Skip to content

Commit 9018361

Browse files
committed
feat: enable detector selection by tags
Register detector ProducedEvent.Tags as event sets, allowing users to select detectors using --events <tag>. Includes unit and integration tests.
1 parent 1ca9325 commit 9018361

6 files changed

Lines changed: 386 additions & 10 deletions

File tree

docs/docs/flags/events.1.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
---
1+
--
22
title: TRACEE-EVENTS
33
section: 1
44
header: Tracee Events Flag Manual
@@ -113,6 +113,24 @@ Threat selection can be combined with regular events. Multiple `--events` flags
113113

114114
**Note:** Detector selection based on threat properties is performed once at startup. Matching detectors are enabled; non-matching detectors are never loaded.
115115

116+
## DETECTOR TAG SELECTION
117+
118+
Detectors can be categorized with tags. You can select all detectors with a specific tag:
119+
120+
```console
121+
--events containers # All detectors tagged with "containers"
122+
--events malware # All detectors tagged with "malware"
123+
--events detectors # All detectors (existing behavior)
124+
```
125+
126+
Tags can be combined with other selection methods:
127+
128+
```console
129+
--events containers,execve # Detectors with "containers" tag + execve syscall
130+
--events 'threat.severity=critical' # Critical threats (any tag)
131+
--events malware,'threat.severity>=high' # Detectors with "malware" tag OR high+ severity threats
132+
```
133+
116134
## EXAMPLES
117135

118136
- To trace only 'execve' and 'open' events, use the following flag:

docs/docs/policies/rules.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ rules:
103103
- event: threat.name=process_injection
104104
```
105105

106+
#### Selecting by Detector Tags
107+
108+
Detectors can be categorized with tags. Select all detectors with a specific tag:
109+
110+
```yaml
111+
rules:
112+
- event: containers # All detectors with "containers" tag
113+
- event: malware # All detectors with "malware" tag
114+
```
115+
106116
#### Combining with Regular Events
107117

108118
Threat-based selection can be combined with regular event selection:

docs/man/events.1

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
.\" Automatically generated by Pandoc 3.2
22
.\"
3-
.TH "TRACEE\-EVENTS" "1" "2024/12" "" "Tracee Events Flag Manual"
3+
.TH "" "" "" "" ""
4+
.PP
5+
\[en] title: TRACEE\-EVENTS section: 1 header: Tracee Events Flag Manual
6+
date: 2024/12 \&...
47
.SS NAME
58
tracee \f[B]\-\-events\f[R] \- Select which events to trace
69
.SS SYNOPSIS
@@ -127,12 +130,34 @@ Supports: =, !=
127130
\-\-events threat.name=process_injection
128131
.EE
129132
.PP
130-
Threat selection can be combined with regular events and is performed at
131-
policy initialization (static, not runtime filtering):
133+
Threat selection can be combined with regular events.
134+
Multiple \f[CR]\-\-events\f[R] flags are combined additively (OR logic):
135+
.IP
136+
.EX
137+
\-\-events threat.severity=critical \-\-events write # Critical threats OR write events
138+
\-\-events fs \-\-events \[aq]threat.severity>=high\[aq] # Filesystem events OR high+ threats
139+
.EE
140+
.PP
141+
\f[B]Note:\f[R] Detector selection based on threat properties is
142+
performed once at startup.
143+
Matching detectors are enabled; non\-matching detectors are never
144+
loaded.
145+
.SS DETECTOR TAG SELECTION
146+
Detectors can be categorized with tags.
147+
You can select all detectors with a specific tag:
148+
.IP
149+
.EX
150+
\-\-events containers # All detectors tagged with \[dq]containers\[dq]
151+
\-\-events malware # All detectors tagged with \[dq]malware\[dq]
152+
\-\-events detectors # All detectors (existing behavior)
153+
.EE
154+
.PP
155+
Tags can be combined with other selection methods:
132156
.IP
133157
.EX
134-
\-\-events threat.severity=critical \-\-events write
135-
\-\-events fs \-\-events \[aq]threat.severity>=high\[aq]
158+
\-\-events containers,execve # Detectors with \[dq]containers\[dq] tag + execve syscall
159+
\-\-events \[aq]threat.severity=critical\[aq] # Critical threats (any tag)
160+
\-\-events malware,\[aq]threat.severity>=high\[aq] # Detectors with \[dq]malware\[dq] tag OR high+ severity threats
136161
.EE
137162
.SS EXAMPLES
138163
.IP \[bu] 2

pkg/cmd/flags/policy_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package flags
22

33
import (
4+
"context"
45
"fmt"
56
"math"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011

12+
pb "github.com/aquasecurity/tracee/api/v1beta1"
13+
"github.com/aquasecurity/tracee/api/v1beta1/detection"
14+
"github.com/aquasecurity/tracee/pkg/detectors"
1115
"github.com/aquasecurity/tracee/pkg/events"
1216
"github.com/aquasecurity/tracee/pkg/filters"
1317
k8s "github.com/aquasecurity/tracee/pkg/k8s/apis/tracee.aquasec.com/v1beta1"
@@ -2488,3 +2492,148 @@ func TestParseEventFilters(t *testing.T) {
24882492
})
24892493
}
24902494
}
2495+
2496+
func TestParseEventFiltersDetectorTags(t *testing.T) {
2497+
// NOTE: Not using t.Parallel() because this test modifies global events.Core state
2498+
2499+
// Save and restore events.Core
2500+
originalCore := events.Core
2501+
defer func() { events.Core = originalCore }()
2502+
2503+
// Create fresh DefinitionGroup with core events
2504+
events.Core = events.NewDefinitionGroup()
2505+
err := events.Core.AddBatch(events.CoreEvents)
2506+
require.NoError(t, err)
2507+
2508+
// Create mock detectors with tags and register their events
2509+
mockDetectors := []detection.EventDetector{
2510+
createMockDetectorForTagTest("det1", "detector_event1", []string{"test_tag_a"}),
2511+
createMockDetectorForTagTest("det2", "detector_event2", []string{"test_tag_b"}),
2512+
createMockDetectorForTagTest("det3", "detector_event3", []string{"test_tag_a", "test_tag_c"}),
2513+
}
2514+
2515+
// Register detector events in events.Core (simulating what happens at startup)
2516+
eventNameToID, err := detectors.CreateEventsFromDetectors(events.StartDetectorID, mockDetectors)
2517+
require.NoError(t, err)
2518+
2519+
testCases := []struct {
2520+
name string
2521+
eventFlags []eventFlag
2522+
validate func(*testing.T, *policy.Policy, map[string]events.ID)
2523+
}{
2524+
{
2525+
name: "detector tag selection - test_tag_a",
2526+
eventFlags: []eventFlag{{
2527+
full: "test_tag_a",
2528+
eventName: "test_tag_a",
2529+
}},
2530+
validate: func(t *testing.T, p *policy.Policy, eMap map[string]events.ID) {
2531+
// Should have detector_event1 and detector_event3 (both have "test_tag_a" tag)
2532+
assert.Contains(t, p.Rules, eMap["detector_event1"])
2533+
assert.Contains(t, p.Rules, eMap["detector_event3"])
2534+
assert.NotContains(t, p.Rules, eMap["detector_event2"])
2535+
assert.Equal(t, 2, len(p.Rules), "Should have exactly 2 events selected")
2536+
},
2537+
},
2538+
{
2539+
name: "detector tag selection - test_tag_b",
2540+
eventFlags: []eventFlag{{
2541+
full: "test_tag_b",
2542+
eventName: "test_tag_b",
2543+
}},
2544+
validate: func(t *testing.T, p *policy.Policy, eMap map[string]events.ID) {
2545+
// Should have only detector_event2
2546+
assert.Contains(t, p.Rules, eMap["detector_event2"])
2547+
assert.NotContains(t, p.Rules, eMap["detector_event1"])
2548+
assert.NotContains(t, p.Rules, eMap["detector_event3"])
2549+
assert.Equal(t, 1, len(p.Rules), "Should have exactly 1 event selected")
2550+
},
2551+
},
2552+
{
2553+
name: "detector tag with regular event",
2554+
eventFlags: []eventFlag{
2555+
{
2556+
full: "test_tag_a",
2557+
eventName: "test_tag_a",
2558+
},
2559+
{
2560+
full: "write",
2561+
eventName: "write",
2562+
},
2563+
},
2564+
validate: func(t *testing.T, p *policy.Policy, eMap map[string]events.ID) {
2565+
// Should have test_tag_a detectors + write syscall
2566+
assert.Contains(t, p.Rules, eMap["detector_event1"])
2567+
assert.Contains(t, p.Rules, eMap["detector_event3"])
2568+
assert.Contains(t, p.Rules, events.Write)
2569+
assert.Equal(t, 3, len(p.Rules), "Should have 3 events selected")
2570+
},
2571+
},
2572+
{
2573+
name: "all detectors set",
2574+
eventFlags: []eventFlag{{
2575+
full: "detectors",
2576+
eventName: "detectors",
2577+
}},
2578+
validate: func(t *testing.T, p *policy.Policy, eMap map[string]events.ID) {
2579+
// Should have all detector events (including built-in detectors, so check for ours)
2580+
assert.Contains(t, p.Rules, eMap["detector_event1"])
2581+
assert.Contains(t, p.Rules, eMap["detector_event2"])
2582+
assert.Contains(t, p.Rules, eMap["detector_event3"])
2583+
// Note: may have more than 3 if there are built-in detectors
2584+
assert.GreaterOrEqual(t, len(p.Rules), 3, "Should have at least our 3 detector events")
2585+
},
2586+
},
2587+
}
2588+
2589+
for _, tc := range testCases {
2590+
t.Run(tc.name, func(t *testing.T) {
2591+
// Don't use t.Parallel() here because we need the events.Core state from parent test
2592+
// Create fresh policy for each test case
2593+
p := policy.NewPolicy()
2594+
2595+
err := parseEventFilters(p, tc.eventFlags, mockDetectors)
2596+
require.NoError(t, err)
2597+
2598+
if tc.validate != nil {
2599+
tc.validate(t, p, eventNameToID)
2600+
}
2601+
})
2602+
}
2603+
}
2604+
2605+
// createMockDetectorForTagTest creates a mock detector for tag testing
2606+
func createMockDetectorForTagTest(id, eventName string, tags []string) detection.EventDetector {
2607+
return &mockDetectorForPolicyTest{
2608+
id: id,
2609+
eventName: eventName,
2610+
tags: tags,
2611+
}
2612+
}
2613+
2614+
// mockDetectorForPolicyTest implements detection.EventDetector for policy testing
2615+
type mockDetectorForPolicyTest struct {
2616+
id string
2617+
eventName string
2618+
tags []string
2619+
}
2620+
2621+
func (m *mockDetectorForPolicyTest) GetDefinition() detection.DetectorDefinition {
2622+
return detection.DetectorDefinition{
2623+
ID: m.id,
2624+
ProducedEvent: pb.EventDefinition{
2625+
Name: m.eventName,
2626+
Description: "Mock detector for policy test",
2627+
Tags: m.tags,
2628+
},
2629+
Requirements: detection.DetectorRequirements{},
2630+
}
2631+
}
2632+
2633+
func (m *mockDetectorForPolicyTest) Init(params detection.DetectorParams) error {
2634+
return nil
2635+
}
2636+
2637+
func (m *mockDetectorForPolicyTest) OnEvent(ctx context.Context, event *pb.Event) ([]detection.DetectorOutput, error) {
2638+
return nil, nil
2639+
}

pkg/detectors/events.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ func CreateEventsFromDetectors(startID events.ID, detectors []detection.EventDet
9999
def.ProducedEvent.Description, // description
100100
false, // internal
101101
false, // syscall
102-
[]string{"detectors", "default"}, // sets
103-
events.NewDependencyStrategy(dependencies), // deps
104-
convertFieldsToDataFields(def.ProducedEvent.Fields), // fields
105-
map[string]interface{}{"detectorID": def.ID}, // properties
102+
append([]string{"detectors", "default"}, def.ProducedEvent.Tags...), // sets - include detector tags
103+
events.NewDependencyStrategy(dependencies), // deps
104+
convertFieldsToDataFields(def.ProducedEvent.Fields), // fields
105+
map[string]interface{}{"detectorID": def.ID}, // properties
106106
)
107107

108108
// Add to events.Core

0 commit comments

Comments
 (0)