diff --git a/cmd/tetra/getevents/getevents.go b/cmd/tetra/getevents/getevents.go index ed40d071106..2e110b7536c 100644 --- a/cmd/tetra/getevents/getevents.go +++ b/cmd/tetra/getevents/getevents.go @@ -38,6 +38,7 @@ type Opts struct { StackTraces bool ImaHash bool PolicyNames []string + CelExpression []string } var Options Opts @@ -85,6 +86,9 @@ var GetFilter = func() *tetragon.Filter { if len(Options.PolicyNames) > 0 { filter.PolicyNames = Options.PolicyNames } + if len(Options.CelExpression) > 0 { + filter.CelExpression = Options.CelExpression + } return &filter } @@ -222,5 +226,6 @@ redirection of events to the stdin. Examples: flags.BoolVar(&Options.StackTraces, "stack-traces", true, "Include stack traces in compact output") flags.BoolVar(&Options.ImaHash, "ima-hash", true, "Include ima hashes in compact output") flags.StringSliceVar(&Options.PolicyNames, "policy-names", nil, "Get events by tracing policy names") + flags.StringSliceVar(&Options.CelExpression, "cel-expression", nil, "Get events satisfying the CEL expression") return &cmd } diff --git a/cmd/tetra/getevents/getevents_test.go b/cmd/tetra/getevents/getevents_test.go index 290fa99cb4f..0d8e0ce09ac 100644 --- a/cmd/tetra/getevents/getevents_test.go +++ b/cmd/tetra/getevents/getevents_test.go @@ -130,4 +130,25 @@ func Test_GetEvents_FilterFields(t *testing.T) { assert.NotEmpty(t, res.GetProcessExec().Parent) } }) + + t.Run("FilterCelExpression", func(t *testing.T) { + testutils.MockPipedFile(t, testutils.RepoRootPath("testdata/events.json")) + cmd := New() + cmd.SetArgs([]string{"--cel-expression", "process_exec.process.pod.pod_labels['class'] == 'deathstar'"}) + output := testutils.RedirectStdoutExecuteCmd(t, cmd) + // remove last trailing newline for splitting + output = bytes.TrimSpace(output) + lines := bytes.Split(output, []byte("\n")) + assert.Equal(t, 2, len(lines)) + for _, line := range lines { + var res tetragon.GetEventsResponse + err := json.Unmarshal(line, &res) + if err != nil { + t.Fatal(err) + } + class, ok := res.GetProcessExec().Process.Pod.PodLabels["class"] + assert.True(t, ok, "Class label should be present") + assert.Equal(t, class, "deathstar") + } + }) } diff --git a/pkg/filters/filters.go b/pkg/filters/filters.go index 648f7457b02..4cde2b55576 100644 --- a/pkg/filters/filters.go +++ b/pkg/filters/filters.go @@ -14,6 +14,7 @@ import ( hubbleFilters "github.com/cilium/cilium/pkg/hubble/filters" "github.com/cilium/tetragon/api/v1/tetragon" "github.com/cilium/tetragon/api/v1/tetragon/codegen/helpers" + "github.com/sirupsen/logrus" ) // ParseFilterList parses a list of process filters in JSON format into protobuf messages. @@ -95,6 +96,7 @@ var Filters = []OnBuildFilter{ &PodRegexFilter{}, &PolicyNamesFilter{}, &CapsFilter{}, + NewCELExpressionFilter(logrus.New()), } func GetProcess(event *v1.Event) *tetragon.Process { diff --git a/pkg/filters/filters_test.go b/pkg/filters/filters_test.go index b8d66e1d537..2d903403dc4 100644 --- a/pkg/filters/filters_test.go +++ b/pkg/filters/filters_test.go @@ -33,7 +33,8 @@ func TestParseFilterList(t *testing.T) { {"pid_set":[1]} {"event_set":["PROCESS_EXEC", "PROCESS_EXIT", "PROCESS_KPROBE", "PROCESS_TRACEPOINT"]} {"arguments_regex":["^--version$","^-a -b -c$"]} -{"capabilities": {"effective": {"all": ["CAP_BPF", "CAP_SYS_ADMIN"]}}}` +{"capabilities": {"effective": {"all": ["CAP_BPF", "CAP_SYS_ADMIN"]}}} +{"cel_expression": ["process_exec.process.bad_field_name == 'curl'"]}` filterProto, err := ParseFilterList(f, true) assert.NoError(t, err) if diff := cmp.Diff( @@ -50,6 +51,7 @@ func TestParseFilterList(t *testing.T) { All: []tetragon.CapabilitiesType{tetragon.CapabilitiesType_CAP_BPF, tetragon.CapabilitiesType_CAP_SYS_ADMIN}, }, }}, + {CelExpression: []string{"process_exec.process.bad_field_name == 'curl'"}}, }, filterProto, cmpopts.IgnoreUnexported(tetragon.Filter{}),