Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add kprobe read/write e2e test #3374

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
254 changes: 254 additions & 0 deletions tests/e2e/tests/kprobe/kprobe_test.go
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is probably from the skeleton test, so maybe it's worth removing it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed this

2d78430

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the buffer from the user-space buffer is generally an unsafe pattern. There is no issue for the test, but I don't know if adding this anti-pattern to our code is a good idea.

We also don't seem to validate the buffer so maybe it would maybe sense to remove this argument?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed args.

6c1044d

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed args.

6c1044d

- index: 2
type: "size_t"
selectors:
- matchPIDs:
- operator: NotIn
followForks: true
isNamespacePID: true
values:
- 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the matchPIDs filter needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pasted from example write.yaml.
I removed it.

6c1044d

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++
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to note is that the cat or echo implementations might change to use other system calls.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cat and echo moved to variables.

3de0f92


return false, nil
}

func (k *kprobeCheker) FinalCheck(logger *logrus.Logger) error {
if k.matches > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check == 2 or at least >= 2 here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed this

9dadd59

return nil
}
return fmt.Errorf("kprobe checker failed, had %d matches", k.matches)
}
Loading