From 197242deb767a29df10c22e5b44530e8e5362dcb Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:55:12 +0900 Subject: [PATCH 1/6] add kprobe read/write e2e test Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 254 ++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 tests/e2e/tests/kprobe/kprobe_test.go diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go new file mode 100644 index 00000000000..74c6831f1a0 --- /dev/null +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +// This package contains a simple test skeleton that can be copied, pasted, and modified +// to create new Tetragon e2e tests. +package kprobe_test + +import ( + "context" + _ "embed" + "errors" + "fmt" + "github.com/cilium/tetragon/api/v1/tetragon" + "github.com/cilium/tetragon/tests/e2e/helpers/grpc" + "github.com/sirupsen/logrus" + "testing" + "time" + + ec "github.com/cilium/tetragon/api/v1/tetragon/codegen/eventchecker" + "github.com/cilium/tetragon/tests/e2e/checker" + "github.com/cilium/tetragon/tests/e2e/helpers" + "github.com/cilium/tetragon/tests/e2e/runners" + "k8s.io/klog/v2" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) + +// This holds our test environment which we get from calling runners.NewRunner().Setup() +var runner *runners.Runner + +// The namespace where we want to spawn our pods +const namespace = "kprobe-test" + +var ( + // Basic Tetragon parameters + TetragonNamespace = "kube-system" + TetragonAppNameKey = "app.kubernetes.io/name" + TetragonAppNameVal = "tetragon" + TetragonContainer = "tetragon" + TetragonCLI = "tetra" +) + +func TestMain(m *testing.M) { + runner = runners.NewRunner().Init() + + // Here we ensure our test namespace doesn't already exist then create it. + runner.Setup(func(ctx context.Context, c *envconf.Config) (context.Context, error) { + ctx, _ = helpers.DeleteNamespace(namespace, true)(ctx, c) + + ctx, err := helpers.CreateNamespace(namespace, true)(ctx, c) + if err != nil { + return ctx, fmt.Errorf("failed to create namespace: %w", err) + } + + return ctx, nil + }) + + // Run the tests using the test runner. + runner.Run(m) +} + +func TestKprobeTracingPolicy(t *testing.T) { + runner.SetupExport(t) + + // Create an curl event checker with a limit or 10 events or 30 seconds, whichever comes first + checker := kprobeChecker().WithEventLimit(20).WithTimeLimit(30 * time.Second) + + // Define test features here. These can be used to perform actions like: + // - Spawning an event checker and running checks + // - Modifying resources in the cluster + // - etc. + + // This starts curlChecker and uses it to run event checks. + runEventChecker := features.New("Run Event Checks"). + Assess("Run Event Checks", + checker.CheckWithFilters( + // allow list + 30*time.Second, + []*tetragon.Filter{{ + EventSet: []tetragon.EventType{tetragon.EventType_PROCESS_KPROBE}, + Namespace: []string{namespace}, + }}, + // deny list + []*tetragon.Filter{}, + )).Feature() + + // This feature waits for curlChecker to start then runs a custom workload. + runWorkload := features.New("Run kprobe test"). + Assess("Install policy", func(ctx context.Context, _ *testing.T, c *envconf.Config) context.Context { + ctx, err := helpers.LoadCRDString(namespace, kprobeReadWritePolicy, false)(ctx, c) + if err != nil { + klog.ErrorS(err, "failed to install policy") + t.Fail() + } + return ctx + }). + Assess("Wait for policy", func(ctx context.Context, _ *testing.T, _ *envconf.Config) context.Context { + if err := grpc.WaitForTracingPolicy(ctx, "sys-write"); err != nil { + klog.ErrorS(err, "failed to wait for policy") + t.Fail() + } + return ctx + }). + Assess("Wait for Checker", checker.Wait(30*time.Second)). + Assess("Start pods", func(ctx context.Context, _ *testing.T, c *envconf.Config) context.Context { + var err error + for _, pod := range []string{ubuntuReadPod, ubuntuWritePod} { + ctx, err = helpers.LoadCRDString(namespace, pod, true)(ctx, c) + if err != nil { + klog.ErrorS(err, "failed to load pod") + t.Fail() + } + + } + return ctx + }). + Assess("Uninstall policy", func(ctx context.Context, _ *testing.T, c *envconf.Config) context.Context { + ctx, err := helpers.UnloadCRDString(namespace, kprobeReadWritePolicy, true)(ctx, c) + if err != nil { + klog.ErrorS(err, "failed to uninstall policy") + t.Fail() + } + return ctx + }).Feature() + + runner.TestInParallel(t, runEventChecker, runWorkload) +} + +const kprobeReadWritePolicy = ` +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicyNamespaced +metadata: + name: "sys-read-write" +spec: + kprobes: + # write system call tracing + - call: "sys_write" + syscall: true + args: + - index: 0 + type: "int" + - index: 1 + type: "char_buf" + sizeArgIndex: 3 + - index: 2 + type: "size_t" + selectors: + - matchPIDs: + - operator: NotIn + followForks: true + isNamespacePID: true + values: + - 1 + matchArgs: + - index: 0 + operator: "Equal" + values: + - "1" + # read system call tracing + - call: "sys_read" + syscall: true + args: + - index: 0 + type: "int" + - index: 1 + type: "char_buf" + sizeArgIndex: 3 + - index: 2 + type: "size_t" + selectors: + - matchPIDs: + - operator: NotIn + followForks: true + isNamespacePID: true + values: + - 1 + matchArgs: + - index: 0 + operator: "Equal" + values: + - "0" +` + +const ubuntuWritePod = ` +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + labels: + run: ubuntu-write + name: ubuntu-write +spec: + containers: + - args: + - /usr/bin/echo + - hello + image: ubuntu + name: ubuntu-write + resources: {} + dnsPolicy: ClusterFirst + restartPolicy: Always +` + +const ubuntuReadPod = ` +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + labels: + run: ubuntu-read + name: ubuntu-read +spec: + containers: + - args: + - cat + - /etc/hostname + image: ubuntu + name: ubuntu-read + resources: {} + dnsPolicy: ClusterFirst + restartPolicy: Always +` + +func kprobeChecker() *checker.RPCChecker { + return checker.NewRPCChecker(&kprobeCheker{}, "kprobe-checker") +} + +type kprobeCheker struct { + matches int +} + +func (k *kprobeCheker) NextEventCheck(event ec.Event, _ *logrus.Logger) (bool, error) { + // ignore other events + ev, ok := event.(*tetragon.ProcessKprobe) + if !ok { + return false, errors.New("not a process kprobe") + } + + if ev.GetFunctionName() == "__x64_sys_write" && ev.GetProcess().GetBinary() == "/usr/bin/echo" { + k.matches++ + } + if ev.GetFunctionName() == "__x64_sys_read" && ev.GetProcess().GetBinary() == "cat" { + k.matches++ + } + + return false, nil +} + +func (k *kprobeCheker) FinalCheck(logger *logrus.Logger) error { + if k.matches > 0 { + return nil + } + return fmt.Errorf("kprobe checker failed, had %d matches", k.matches) +} From 2d78430639bd10380eade66a97b3b2f7255c8995 Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Sat, 8 Feb 2025 09:37:03 +0900 Subject: [PATCH 2/6] delete copyright comment Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go index 74c6831f1a0..e0770355b53 100644 --- a/tests/e2e/tests/kprobe/kprobe_test.go +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -1,8 +1,3 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Tetragon - -// This package contains a simple test skeleton that can be copied, pasted, and modified -// to create new Tetragon e2e tests. package kprobe_test import ( From 9dadd59644d3ff31a515206e1a446e341ced81c3 Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Sat, 8 Feb 2025 09:54:49 +0900 Subject: [PATCH 3/6] fix final check count Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go index e0770355b53..1b71fe49503 100644 --- a/tests/e2e/tests/kprobe/kprobe_test.go +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -242,7 +242,7 @@ func (k *kprobeCheker) NextEventCheck(event ec.Event, _ *logrus.Logger) (bool, e } func (k *kprobeCheker) FinalCheck(logger *logrus.Logger) error { - if k.matches > 0 { + if k.matches >= 2 { return nil } return fmt.Errorf("kprobe checker failed, had %d matches", k.matches) From 6c1044dfb9e583ffe8f67b45fa9df635be517982 Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:11:54 +0900 Subject: [PATCH 4/6] remove args from TracingPolicy Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 45 +-------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go index 1b71fe49503..d1c977ea88d 100644 --- a/tests/e2e/tests/kprobe/kprobe_test.go +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -122,58 +122,15 @@ func TestKprobeTracingPolicy(t *testing.T) { } const kprobeReadWritePolicy = ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicyNamespaced +apiVersion: cilium.io/v1alpha1kind: TracingPolicyNamespaced metadata: name: "sys-read-write" spec: kprobes: - # write system call tracing - call: "sys_write" syscall: true - args: - - index: 0 - type: "int" - - index: 1 - type: "char_buf" - sizeArgIndex: 3 - - index: 2 - type: "size_t" - selectors: - - matchPIDs: - - operator: NotIn - followForks: true - isNamespacePID: true - values: - - 1 - matchArgs: - - index: 0 - operator: "Equal" - values: - - "1" - # read system call tracing - call: "sys_read" syscall: true - args: - - index: 0 - type: "int" - - index: 1 - type: "char_buf" - sizeArgIndex: 3 - - index: 2 - type: "size_t" - selectors: - - matchPIDs: - - operator: NotIn - followForks: true - isNamespacePID: true - values: - - 1 - matchArgs: - - index: 0 - operator: "Equal" - values: - - "0" ` const ubuntuWritePod = ` From 3de0f92b95642893b417289f6de5f801529b9823 Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:16:04 +0900 Subject: [PATCH 5/6] cat and echo system calls set to variables Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go index d1c977ea88d..eefb92febed 100644 --- a/tests/e2e/tests/kprobe/kprobe_test.go +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -33,6 +33,8 @@ var ( TetragonAppNameVal = "tetragon" TetragonContainer = "tetragon" TetragonCLI = "tetra" + readCmd = "cat" + writeCmd = "/usr/bin/echo" ) func TestMain(m *testing.M) { @@ -133,7 +135,7 @@ spec: syscall: true ` -const ubuntuWritePod = ` +var ubuntuWritePod = fmt.Sprintf(` apiVersion: v1 kind: Pod metadata: @@ -144,16 +146,16 @@ metadata: spec: containers: - args: - - /usr/bin/echo + - %s - hello image: ubuntu name: ubuntu-write resources: {} dnsPolicy: ClusterFirst restartPolicy: Always -` +`, writeCmd) -const ubuntuReadPod = ` +var ubuntuReadPod = fmt.Sprintf(` apiVersion: v1 kind: Pod metadata: @@ -164,14 +166,14 @@ metadata: spec: containers: - args: - - cat + - %s - /etc/hostname image: ubuntu name: ubuntu-read resources: {} dnsPolicy: ClusterFirst restartPolicy: Always -` +`, readCmd) func kprobeChecker() *checker.RPCChecker { return checker.NewRPCChecker(&kprobeCheker{}, "kprobe-checker") @@ -188,10 +190,10 @@ func (k *kprobeCheker) NextEventCheck(event ec.Event, _ *logrus.Logger) (bool, e return false, errors.New("not a process kprobe") } - if ev.GetFunctionName() == "__x64_sys_write" && ev.GetProcess().GetBinary() == "/usr/bin/echo" { + if ev.GetFunctionName() == "__x64_sys_write" && ev.GetProcess().GetBinary() == writeCmd { k.matches++ } - if ev.GetFunctionName() == "__x64_sys_read" && ev.GetProcess().GetBinary() == "cat" { + if ev.GetFunctionName() == "__x64_sys_read" && ev.GetProcess().GetBinary() == readCmd { k.matches++ } From e4db7ef27810dd6bfc20ebf2b6d7e74261116f50 Mon Sep 17 00:00:00 2001 From: sat0ken <15720506+sat0ken@users.noreply.github.com> Date: Thu, 13 Feb 2025 21:41:05 +0900 Subject: [PATCH 6/6] fix TracingPolicyNamespaced yaml file Signed-off-by: sat0ken <15720506+sat0ken@users.noreply.github.com> --- tests/e2e/tests/kprobe/kprobe_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/tests/kprobe/kprobe_test.go b/tests/e2e/tests/kprobe/kprobe_test.go index eefb92febed..f95638219bd 100644 --- a/tests/e2e/tests/kprobe/kprobe_test.go +++ b/tests/e2e/tests/kprobe/kprobe_test.go @@ -124,7 +124,8 @@ func TestKprobeTracingPolicy(t *testing.T) { } const kprobeReadWritePolicy = ` -apiVersion: cilium.io/v1alpha1kind: TracingPolicyNamespaced +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicyNamespaced metadata: name: "sys-read-write" spec: