From 687cdb170a2d54798d425c5680ad4e975e9b018c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 5 Dec 2024 11:42:22 -0300 Subject: [PATCH] chore(cmd): introduce evt program Initially, the evt program is able to trigger events in the system by the use of the `trigger` command. --- Makefile | 35 ++++++- cmd/evt/cmd/helpers/helpers.go | 31 ++++++ cmd/evt/cmd/root.go | 37 +++++++ cmd/evt/cmd/trigger/cobra.go | 128 ++++++++++++++++++++++++ cmd/evt/cmd/trigger/trigger.go | 173 +++++++++++++++++++++++++++++++++ cmd/evt/main.go | 24 +++++ 6 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 cmd/evt/cmd/helpers/helpers.go create mode 100644 cmd/evt/cmd/root.go create mode 100644 cmd/evt/cmd/trigger/cobra.go create mode 100644 cmd/evt/cmd/trigger/trigger.go create mode 100644 cmd/evt/main.go diff --git a/Makefile b/Makefile index 8ca4813a7321..cb2624290b7e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: all | env -all: tracee-ebpf tracee-rules signatures tracee +all: tracee-ebpf tracee-rules signatures tracee evt # # make @@ -611,6 +611,39 @@ clean-signatures: # other commands # +# evt + +EVT_SRC_DIRS = ./cmd/evt +EVT_SRC = $(shell find $(EVT_SRC_DIRS) \ + -type f \ + -name '*.go' \ + ! -name '*_test.go' \ + ) +EVT_TRIGGERS_DIR = $(EVT_SRC_DIRS)/cmd/trigger/triggers + +.PHONY: evt +evt: $(OUTPUT_DIR)/evt + +$(OUTPUT_DIR)/evt: \ + $(EVT_SRC) \ + $(OUTPUT_DIR)/tracee \ + | .eval_goenv \ + .checkver_$(CMD_GO) \ +# + $(GO_ENV_EBPF) $(CMD_GO) build \ + -ldflags="$(GO_DEBUG_FLAG) \ + " \ + -v -o $@ \ + ./cmd/evt + cp -r $(EVT_TRIGGERS_DIR) $(OUTPUT_DIR)/evt-triggers + + +.PHONY: clean-evt +clean-evt: +# + $(CMD_RM) -rf $(OUTPUT_DIR)/evt + $(CMD_RM) -rf $(OUTPUT_DIR)/evt-triggers + # tracee-bench TRACEE_BENCH_SRC_DIRS = ./cmd/tracee-bench/ diff --git a/cmd/evt/cmd/helpers/helpers.go b/cmd/evt/cmd/helpers/helpers.go new file mode 100644 index 000000000000..05a08a47b409 --- /dev/null +++ b/cmd/evt/cmd/helpers/helpers.go @@ -0,0 +1,31 @@ +package helpers + +import ( + "fmt" + "io" + "path/filepath" +) + +type PrefixWriter struct { + Prefix []byte + Writer io.Writer +} + +// Write writes the given bytes with the prefix +func (pw *PrefixWriter) Write(p []byte) (int, error) { + return pw.Writer.Write(append(pw.Prefix, p...)) +} + +const ( + MaxCommLen = 16 +) + +func GetFilterOutCommScope(cmd string) string { + comm := filepath.Base(cmd) + comm = comm[:min(len(comm), MaxCommLen-1)] + return fmt.Sprintf("comm!=%s", comm) +} + +func GetFilterInTreeScope(pid string) string { + return fmt.Sprintf("tree=%s", pid) +} diff --git a/cmd/evt/cmd/root.go b/cmd/evt/cmd/root.go new file mode 100644 index 000000000000..3cab601702ca --- /dev/null +++ b/cmd/evt/cmd/root.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "context" + "os" + + "github.com/spf13/cobra" + + "github.com/aquasecurity/tracee/cmd/evt/cmd/trigger" +) + +func init() { + rootCmd.AddCommand(trigger.Cmd()) +} + +var ( + rootCmd = &cobra.Command{ + Use: "evt", + Short: "An event testing tool", + Long: "evt is a simple testing tool that generates events to stress the system", + } +) + +func initRootCmd() error { + rootCmd.SetOutput(os.Stdout) + rootCmd.SetErr(os.Stderr) + + return nil +} + +func Execute(ctx context.Context) error { + if err := initRootCmd(); err != nil { + return err + } + + return rootCmd.ExecuteContext(ctx) +} diff --git a/cmd/evt/cmd/trigger/cobra.go b/cmd/evt/cmd/trigger/cobra.go new file mode 100644 index 000000000000..ad9be75a4ac0 --- /dev/null +++ b/cmd/evt/cmd/trigger/cobra.go @@ -0,0 +1,128 @@ +package trigger + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" +) + +var ( + triggerCmd = &cobra.Command{ + Use: "trigger", + Aliases: []string{"t"}, + Short: "Trigger events to trigger", + RunE: triggerCmdRun, + SilenceErrors: true, + SilenceUsage: true, + } +) + +const ( + defaultTriggerOps = int32(1) + defaultTriggerSleep = 10 * time.Nanosecond + triggerTimeout = 30 * time.Minute +) + +func init() { + triggerCmd.Flags().StringP( + "event", + "e", + "", + "...\t\tSelect event to trigger", + ) + if err := triggerCmd.MarkFlagRequired("event"); err != nil { + triggerCmd.PrintErrf("marking required flag: %v\n", err) + os.Exit(1) + } + + triggerCmd.Flags().Int32P( + "ops", + "o", + defaultTriggerOps, + "...\t\tNumber of operations to perform", + ) + + triggerCmd.Flags().DurationP( + "sleep", + "s", + defaultTriggerSleep, + "...\t\tSleep time between operations", + ) + + triggerCmd.Flags().BoolP( + "bypass-flags", + "f", + false, + "\t\t\tPrint tracee bypass flags", + ) + + triggerCmd.Flags().BoolP( + "wait-signal", + "w", + false, + "\t\t\tWait for start signal (SIGUSR1)", + ) +} + +func getTrigger(cmd *cobra.Command) (*trigger, error) { + event, err := cmd.Flags().GetString("event") + if err != nil { + return nil, err + } + + ops, err := cmd.Flags().GetInt32("ops") + if err != nil { + return nil, err + } + if ops <= 0 { + return nil, fmt.Errorf("ops must be greater than 0") + } + + sleep, err := cmd.Flags().GetDuration("sleep") + if err != nil { + return nil, err + } + + bypassFlags, err := cmd.Flags().GetBool("bypass-flags") + if err != nil { + return nil, err + } + + waitSignal, err := cmd.Flags().GetBool("wait-signal") + if err != nil { + return nil, err + } + + return &trigger{ + event: event, + ops: ops, + sleep: sleep, + printBypassFlags: bypassFlags, + waitSignal: waitSignal, + cmd: cmd, + }, nil +} + +func triggerCmdRun(cmd *cobra.Command, args []string) error { + t, err := getTrigger(cmd) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeoutCause( + t.cmd.Context(), + triggerTimeout, + fmt.Errorf("timeout after %v", triggerTimeout), + ) + defer cancel() + t.ctx = ctx + + return t.run() +} + +func Cmd() *cobra.Command { + return triggerCmd +} diff --git a/cmd/evt/cmd/trigger/trigger.go b/cmd/evt/cmd/trigger/trigger.go new file mode 100644 index 000000000000..38bf489ce27d --- /dev/null +++ b/cmd/evt/cmd/trigger/trigger.go @@ -0,0 +1,173 @@ +package trigger + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/spf13/cobra" + + "github.com/aquasecurity/tracee/cmd/evt/cmd/helpers" +) + +const ( + signalWaitTimeout = 1 * time.Minute +) + +type trigger struct { + event string + ops int32 + sleep time.Duration + waitSignal bool + printBypassFlags bool + + ctx context.Context + cmd *cobra.Command +} + +func (t *trigger) run() error { + t.setCmdOutErr() + + if t.printBypassFlags { + t.printTraceeBypassFlags() + os.Exit(0) + } + + err := t.waitForSignal() + if err != nil { + return err + } + + return t.runTriggers() +} + +func (t *trigger) runTriggers() error { + triggerPath, err := getTriggerPath(t.event) + if err != nil { + return fmt.Errorf("failed to get trigger path: %w", err) + } + + const layout = "15:04:05.999999999" + now := time.Now() + t.printf("Starting triggering %d ops with %v sleep time at %v\n", t.ops, t.sleep, now.Format(layout)) + + for i := int32(0); i < t.ops; i++ { + select { + case <-t.ctx.Done(): + t.printf("Stopping triggering: %v\n", t.ctx.Err()) + return t.ctx.Err() + default: + time.Sleep(t.sleep) + } + + exeCmd := exec.CommandContext(t.ctx, triggerPath) + err := exeCmd.Run() + if err != nil { + return fmt.Errorf("failed to run command: %w", err) + } + } + + end := time.Now() + t.printf("Finished triggering %d ops at %v after %v\n", t.ops, end.Format(layout), end.Sub(now).String()) + + return nil +} + +func (t *trigger) printf(format string, args ...interface{}) { + t.cmd.Printf(format, args...) +} + +func (t *trigger) println(args ...interface{}) { + t.cmd.Println(args...) +} + +func (t *trigger) setCmdOutErr() { + if !t.waitSignal { + return + } + + prefix := []byte(fmt.Sprintf("[trigger:%d:%s] ", os.Getpid(), t.event)) + cmd := t.cmd + cmd.SetOut(&helpers.PrefixWriter{ + Prefix: prefix, + Writer: os.Stdout, + }) + cmd.SetErr(&helpers.PrefixWriter{ + Prefix: prefix, + Writer: os.Stderr, + }) +} + +func (t *trigger) waitForSignal() error { + if !t.waitSignal { + return nil + } + + startChan := make(chan os.Signal, 1) + signal.Notify(startChan, syscall.SIGUSR1) + t.println("Waiting for start signal SIGUSR1") + + ctx := t.ctx + timeout := time.After(signalWaitTimeout) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-startChan: + return nil + case <-timeout: + return fmt.Errorf("timed out waiting for signal SIGUSR1") + } +} + +func (t *trigger) printTraceeBypassFlags() { + parentShell, ok := os.LookupEnv("SHELL") + if ok { + parentShell = filepath.Base(parentShell) + parentShell = helpers.GetFilterOutCommScope(parentShell) + } + + triggersInterpreter := helpers.GetFilterOutCommScope("sh") // scripts interpreter + selfComm := helpers.GetFilterOutCommScope(os.Args[0]) + triggerComm := helpers.GetFilterOutCommScope(fmt.Sprintf("%s.sh", t.event)) + t.printf("Tracee bypass flags: %s\n", getScopeFlags(triggersInterpreter, parentShell, selfComm, triggerComm)) + + parentPid := helpers.GetFilterInTreeScope(fmt.Sprintf("%d", os.Getppid())) + t.printf("If running trigger from this shell, also use: %s\n", getScopeFlags(parentPid)) +} + +// helpers + +func getTriggerPath(event string) (string, error) { + execPath, err := os.Executable() + if err != nil { + panic(err) + } + + basePath := filepath.Dir(execPath) + triggerPath := filepath.Join(basePath, "evt-triggers", fmt.Sprintf("%s.sh", event)) + _, err = os.Stat(triggerPath) + if err != nil { + return "", err + } + + return triggerPath, nil +} + +func getScopeFlags(flags ...string) string { + var scopes []string + for _, flag := range flags { + if flag == "" { + continue + } + scopes = append(scopes, fmt.Sprintf("-s %s", flag)) + } + + return strings.Join(scopes, " ") +} diff --git a/cmd/evt/main.go b/cmd/evt/main.go new file mode 100644 index 000000000000..31607c1d2380 --- /dev/null +++ b/cmd/evt/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/aquasecurity/tracee/cmd/evt/cmd" +) + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + err := cmd.Execute(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + os.Exit(0) +}