diff --git a/cmd/traceectl/cmd/event.go b/cmd/traceectl/cmd/event.go new file mode 100644 index 000000000000..c4abe945d913 --- /dev/null +++ b/cmd/traceectl/cmd/event.go @@ -0,0 +1,191 @@ +package cmd + +import ( + "context" + "fmt" + "strconv" + "strings" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/client" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/cmd/formatter" + "github.com/spf13/cobra" +) + +var eventCmd = &cobra.Command{ + Use: "event [enable | disable | describe | list]", + Short: "Manage tracee events", + Long: `Manage events in tracee. + + Subcommands: + enable Enable specific events. + disable Disable specific events. + describe Get descriptions of available events. + list List available events. + + Examples: + tracee event enable security_file_open + tracee event describe magic_write + tracee event list + `, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.PrintErrln("Error: no event names provided. Please specify at least one event to enable.") + return + } + }, +} + +func init() { + eventCmd.AddCommand(listEventCmd) + eventCmd.AddCommand(describeEventCmd) + eventCmd.AddCommand(enableEventCmd) + eventCmd.AddCommand(disableEventCmd) + + listEventCmd.Flags().StringVarP(&formatFlag, "format", "f", formatter.FormatTable, "Output format (json|table)") + describeEventCmd.Flags().StringVarP(&formatFlag, "format", "f", formatter.FormatTable, "Output format (json|table)") +} + +var listEventCmd = &cobra.Command{ + Use: "list", + Short: "List available events", + Long: `Lists all available event definitions (built-in and plugin-defined), providing a brief summary of each.`, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + listEvents(cmd, args) + }, +} +var describeEventCmd = &cobra.Command{ + Use: "describe ", + Short: "Describe an event", + Long: `Retrieves the detailed definition of a specific event, including its fields, types, and other metadata.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + getEventDescriptions(cmd, args) + }, +} +var enableEventCmd = &cobra.Command{ + Use: "enable ", + Short: "Enable an event", + Long: `Enables capturing of a specific event type.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + enableEvents(cmd, args[0]) + }, +} +var disableEventCmd = &cobra.Command{ + Use: "disable ", + Short: "Disable an event", + Long: `Disables capturing of a specific event type.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + disableEvents(cmd, args[0]) + }, +} + +func listEvents(cmd *cobra.Command, args []string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + response, err := traceeClient.GetEventDefinitions(context.Background(), &pb.GetEventDefinitionsRequest{EventNames: args}) + if err != nil { + cmd.PrintErrln("Error getting event definitions: ", err) + return + } + format, err := formatter.NewFormatter(formatFlag, cmd) + if err != nil { + cmd.PrintErrln("Error creating formatter: ", err) + return + } + switch format.GetFormat() { + case formatter.FormatJson: + format.PrintJson(response) + case formatter.FormatTable: + format.PrintTableHeaders([]string{"ID", "Name", "Version", "Tags"}) + for _, event := range response.Definitions { + //remove descriptions + format.PrintTableRow(prepareDescription(event)[:4]) + } + default: + cmd.PrintErrln("output format not supported") + return + } +} + +func getEventDescriptions(cmd *cobra.Command, args []string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + response, err := traceeClient.GetEventDefinitions(context.Background(), &pb.GetEventDefinitionsRequest{EventNames: args}) + if err != nil { + cmd.PrintErrln("Error getting event definitions: ", err) + return + + } + format, err := formatter.NewFormatter(formatFlag, cmd) + if err != nil { + cmd.PrintErrln("Error creating formatter: ", err) + return + } + switch format.GetFormat() { + case formatter.FormatJson: + format.PrintJson(response) + case formatter.FormatTable: + format.PrintTableHeaders([]string{"ID", "Name", "Version", "Tags", "Description"}) + for _, event := range response.Definitions { + format.PrintTableRow(prepareDescription(event)) + } + default: + cmd.PrintErrln("output format not supported") + return + + } +} +func prepareDescription(event *pb.EventDefinition) []string { + return []string{ + strconv.Itoa(int(event.Id)), + event.Name, + fmt.Sprintf("%d.%d.%d", event.Version.Major, event.Version.Minor, event.Version.Patch), + strings.Join(event.Tags, ", "), + event.Description, + } + +} +func enableEvents(cmd *cobra.Command, eventName string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + _, err = traceeClient.EnableEvent(context.Background(), &pb.EnableEventRequest{Name: eventName}) + if err != nil { + cmd.PrintErrln("Error enabling event:", err) + return + } + cmd.Printf("Enabled event: %s\n", eventName) +} +func disableEvents(cmd *cobra.Command, eventName string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + _, err = traceeClient.DisableEvent(context.Background(), &pb.DisableEventRequest{Name: eventName}) + if err != nil { + cmd.PrintErrln("Error disabling event:", err) + return + } + cmd.Printf("Disabled event: %s\n", eventName) +} diff --git a/cmd/traceectl/cmd/event_test.go b/cmd/traceectl/cmd/event_test.go new file mode 100644 index 000000000000..ffc8dfde2d6b --- /dev/null +++ b/cmd/traceectl/cmd/event_test.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/cmd/test" +) + +func TestEvent(t *testing.T) { + eventTests := []test.TestCase{ + { + TestName: "event", + OutputSlice: []string{"event"}, + ExpectedPrinter: nil, + ExpectedError: fmt.Errorf("requires at least 1 arg(s), only received 0"), + }, + { + TestName: "events list", + OutputSlice: []string{"event", "list", "--format", "json"}, + ExpectedPrinter: "", + ExpectedError: nil, + }, + { + TestName: "No events describe", + OutputSlice: []string{"event", "describe", "--format", "json"}, + ExpectedPrinter: nil, + ExpectedError: fmt.Errorf("accepts 1 arg(s), received 0"), + }, + { + TestName: "describe ", + OutputSlice: []string{"event", "describe", "event_test1", "--format", "json"}, + ExpectedPrinter: "event_test1", + ExpectedError: nil, + }, + { + TestName: "No events enable", + OutputSlice: []string{"event", "enable"}, + ExpectedPrinter: nil, + ExpectedError: fmt.Errorf("accepts 1 arg(s), received 0"), + }, + { + TestName: "enable event", + OutputSlice: []string{"event", "enable", "event"}, + ExpectedPrinter: "Enabled event: event", + ExpectedError: nil, + }, + { + TestName: "No disable events", + OutputSlice: []string{"event", "disable"}, + ExpectedPrinter: nil, + ExpectedError: fmt.Errorf("accepts 1 arg(s), received 0"), + }, + { + TestName: "disable event", + OutputSlice: []string{"event", "disable", "event"}, + ExpectedPrinter: "Disabled event: event", + ExpectedError: nil, + }, + } + for _, testCase := range eventTests { + t.Run(testCase.TestName, func(t *testing.T) { test.TestCommand(t, testCase, rootCmd) }) + } +} diff --git a/cmd/traceectl/cmd/root.go b/cmd/traceectl/cmd/root.go new file mode 100644 index 000000000000..072f70583acd --- /dev/null +++ b/cmd/traceectl/cmd/root.go @@ -0,0 +1,124 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/client" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/cmd/flags" + + "github.com/spf13/cobra" +) + +var ( + formatFlag string + outputFlag string + server client.ServerInfo = client.ServerInfo{ + ConnectionType: client.Protocol_UNIX, + Addr: client.Socket, + } +) + +var ( + rootCmd = &cobra.Command{ + Use: "traceectl [flags] [command]", + Short: "traceectl is a CLI tool for tracee", + Long: `traceectl is a CLI tool for tracee: +This tool allows you to manage events, stream events directly from tracee, and get info about tracee. +`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + var err error + if err = flags.PrepareOutput(cmd, outputFlag); err != nil { + return err + } + if server, err = flags.PrepareServer(cmd, server); err != nil { + return err + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, + } +) + +func init() { + rootCmd.AddCommand(streamCmd) + rootCmd.AddCommand(eventCmd) + rootCmd.AddCommand(metricsCmd) + rootCmd.AddCommand(versionCmd) + + rootCmd.PersistentFlags().StringVar(&server.Addr, "server", client.Socket, `Server connection path or address. + for unix socket (default: /tmp/tracee.sock) + for tcp `) + rootCmd.PersistentFlags().StringVarP(&outputFlag, "output", "o", "", "Specify the output format") +} + +var metricsCmd = &cobra.Command{ + Use: "metrics [--output ]", + Short: "Display Tracee metrics", + Long: "Retrieves metrics about Tracee's performance and resource usage.", + Run: func(cmd *cobra.Command, args []string) { + displayMetrics(cmd, args) + }, +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Display the version of tracee", + Long: "This is the version of the tracee application you connected to", + Run: func(cmd *cobra.Command, args []string) { + displayVersion(cmd, args) + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func displayMetrics(cmd *cobra.Command, _ []string) { + traceeClient, err := client.NewDiagnosticClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + response, err := traceeClient.GetMetrics(context.Background(), &pb.GetMetricsRequest{}) + if err != nil { + cmd.PrintErrln("Error getting metrics: ", err) + return + } + + fmt.Fprintf(cmd.OutOrStdout(), "EventCount: %d\n", response.EventCount) + fmt.Fprintf(cmd.OutOrStdout(), "EventsFiltered: %d\n", response.EventsFiltered) + fmt.Fprintf(cmd.OutOrStdout(), "NetCapCount: %d\n", response.NetCapCount) + fmt.Fprintf(cmd.OutOrStdout(), "BPFLogsCount: %d\n", response.BPFLogsCount) + fmt.Fprintf(cmd.OutOrStdout(), "ErrorCount: %d\n", response.ErrorCount) + fmt.Fprintf(cmd.OutOrStdout(), "LostEvCount: %d\n", response.LostEvCount) + fmt.Fprintf(cmd.OutOrStdout(), "LostWrCount: %d\n", response.LostWrCount) + fmt.Fprintf(cmd.OutOrStdout(), "LostNtCapCount: %d\n", response.LostNtCapCount) + fmt.Fprintf(cmd.OutOrStdout(), "LostBPFLogsCount: %d\n", response.LostBPFLogsCount) + +} + +func displayVersion(cmd *cobra.Command, _ []string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + response, err := traceeClient.GetVersion(context.Background(), &pb.GetVersionRequest{}) + + if err != nil { + cmd.PrintErrln("Error getting version: ", err) + return + } + fmt.Fprintf(cmd.OutOrStdout(), "Version: %s\n", response.Version) +} diff --git a/cmd/traceectl/cmd/stream.go b/cmd/traceectl/cmd/stream.go new file mode 100644 index 000000000000..0b9f8b3fd42d --- /dev/null +++ b/cmd/traceectl/cmd/stream.go @@ -0,0 +1,139 @@ +package cmd + +import ( + "errors" + "fmt" + "io" + "strconv" + "strings" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/client" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/cmd/formatter" + "github.com/spf13/cobra" +) + +var streamCmd = &cobra.Command{ + Use: "stream [policies...]", + Short: "Stream events from tracee", + Long: `Stream Management: +Stream events directly from tracee to the preferred output format. +`, + Run: func(cmd *cobra.Command, args []string) { + streamEvents(cmd, args) + }, +} + +func init() { + streamCmd.Flags().StringVarP(&formatFlag, "format", "f", formatter.FormatTable, "Specify the output format for streamed events (json|table). Defaults to table.") +} + +func streamEvents(cmd *cobra.Command, args []string) { + traceeClient, err := client.NewServiceClient(server) + if err != nil { + cmd.PrintErrln("Error creating client: ", err) + return + } + defer traceeClient.CloseConnection() + + //request to stream events + req := &pb.StreamEventsRequest{Policies: args} + stream, err := traceeClient.StreamEvents(cmd.Context(), req) + if err != nil { + cmd.PrintErrln("Error calling Stream: ", err) + return + } + //create formatter + format, err := formatter.NewFormatter(formatFlag, cmd) + if err != nil { + cmd.PrintErrln("Error creating formatter: ", err) + return + } + switch format.GetFormat() { + case formatter.FormatJson: + for { + res, err := stream.Recv() + if err != nil { + //End of stream\close connection + if errors.Is(err, io.EOF) { + break + } + cmd.PrintErrln("Error receiving streamed event") + } + format.PrintJson(res.Event) + } + case formatter.FormatTable: + format.PrintTableHeaders([]string{"TIME", "EVENT NAME", "POLICIES", "PID", "DATA"}) + for { + res, err := stream.Recv() + if err != nil { + //End of stream\close connection + if errors.Is(err, io.EOF) { + break + } + cmd.PrintErrln("Error receiving streamed event") + } + format.PrintTableRow(prepareEvent(res.Event)) + } + default: + cmd.PrintErrln("output format not supported") + return + } +} +func prepareEvent(event *pb.Event) []string { + return []string{ + event.Timestamp.AsTime().Format("15:04:05.000"), + event.Name, + strings.Join(event.Policies.Matched, ","), + strconv.Itoa(int(event.Context.Process.Pid.Value)), + getEventData(event.Data), + } + +} +func getEventData(data []*pb.EventValue) string { + var result []string + for _, ev := range data { + result = append(result, getEventName(ev)+getEventValue(ev)) + } + return strings.Join(result, ", ") +} +func getEventName(ev *pb.EventValue) string { + return strings.ToUpper(ev.Name[0:1]) + ev.Name[1:] + ": " +} +func getEventValue(ev *pb.EventValue) string { + switch v := ev.Value.(type) { + case *pb.EventValue_Int32: + return fmt.Sprintf("%d", v.Int32) + case *pb.EventValue_Int64: + return fmt.Sprintf("%d", v.Int64) + case *pb.EventValue_UInt32: + return fmt.Sprintf("%d", v.UInt32) + case *pb.EventValue_UInt64: + return fmt.Sprintf("%d", v.UInt64) + case *pb.EventValue_Str: + return v.Str + case *pb.EventValue_Bytes: + return fmt.Sprintf("%x", v.Bytes) + case *pb.EventValue_Bool: + if v.Bool { + return "true" + } + return "false" + case *pb.EventValue_StrArray: + return strings.Join(v.StrArray.Value, ", ") + case *pb.EventValue_Int32Array: + var result []string + for _, val := range v.Int32Array.Value { + result = append(result, strconv.Itoa(int(val))) + } + return strings.Join(result, ", ") + case *pb.EventValue_UInt64Array: + var result []string + for _, val := range v.UInt64Array.Value { + result = append(result, strconv.Itoa(int(val))) + } + return strings.Join(result, ", ") + default: + return "unknown" + } +} diff --git a/cmd/traceectl/go.mod b/cmd/traceectl/go.mod new file mode 100644 index 000000000000..94d1d8ba2f95 --- /dev/null +++ b/cmd/traceectl/go.mod @@ -0,0 +1,23 @@ +module github.com/aquasecurity/tracee/cmd/traceectl + +go 1.23.1 + +require ( + github.com/aquasecurity/tracee/api v0.0.0-20240918153521-1b3f9e8657e0 + github.com/spf13/cobra v1.8.1 + github.com/stretchr/testify v1.7.1 + google.golang.org/grpc v1.67.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cmd/traceectl/go.sum b/cmd/traceectl/go.sum new file mode 100644 index 000000000000..44de7a6fd20b --- /dev/null +++ b/cmd/traceectl/go.sum @@ -0,0 +1,37 @@ +github.com/aquasecurity/tracee/api v0.0.0-20240918153521-1b3f9e8657e0 h1:3pt2Ceg6urRTUdOaX++LheYObFE4DbS63chktnQu24I= +github.com/aquasecurity/tracee/api v0.0.0-20240918153521-1b3f9e8657e0/go.mod h1:Gn6xVkaBkVe1pOQ0++uuHl+lMMClv0TPY8mCQ6j88aA= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/traceectl/main.go b/cmd/traceectl/main.go new file mode 100644 index 000000000000..9e890e896810 --- /dev/null +++ b/cmd/traceectl/main.go @@ -0,0 +1,10 @@ +/* +Copyright © 2024 Shoham Yosef Bitton +*/ +package main + +import "github.com/aquasecurity/tracee/cmd/traceectl/cmd" + +func main() { + cmd.Execute() +} diff --git a/cmd/traceectl/pkg/client/client.go b/cmd/traceectl/pkg/client/client.go new file mode 100644 index 000000000000..26b9c030fbcc --- /dev/null +++ b/cmd/traceectl/pkg/client/client.go @@ -0,0 +1,28 @@ +package client + +import ( + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +const ( + Protocol_UNIX = "unix" + Protocol_TCP = "tcp" + Socket = "/var/run/tracee.sock" +) + +type ServerInfo struct { + ConnectionType string + Addr string +} + +func connectToServer(server ServerInfo) (*grpc.ClientConn, error) { + var opts []grpc.DialOption + var conn *grpc.ClientConn + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient(server.Addr, opts...) + if err != nil { + return nil, err + } + return conn, nil +} diff --git a/cmd/traceectl/pkg/client/diagnostic.go b/cmd/traceectl/pkg/client/diagnostic.go new file mode 100644 index 000000000000..c62d80778fb7 --- /dev/null +++ b/cmd/traceectl/pkg/client/diagnostic.go @@ -0,0 +1,32 @@ +package client + +import ( + "context" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "google.golang.org/grpc" +) + +type DiagnosticClient struct { + conn *grpc.ClientConn + client pb.DiagnosticServiceClient +} + +func NewDiagnosticClient(serverInfo ServerInfo) (*DiagnosticClient, error) { + conn, err := connectToServer(serverInfo) + if err != nil { + return nil, err + } + return &DiagnosticClient{ + conn: conn, + client: pb.NewDiagnosticServiceClient(conn), + }, nil +} +func (tc *DiagnosticClient) CloseConnection() { + if err := tc.conn.Close(); err != nil { + return + } +} +func (tc *DiagnosticClient) GetMetrics(ctx context.Context, req *pb.GetMetricsRequest) (*pb.GetMetricsResponse, error) { + return tc.client.GetMetrics(ctx, req) +} diff --git a/cmd/traceectl/pkg/client/service.go b/cmd/traceectl/pkg/client/service.go new file mode 100644 index 000000000000..0e6c46a184d6 --- /dev/null +++ b/cmd/traceectl/pkg/client/service.go @@ -0,0 +1,47 @@ +package client + +import ( + "context" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "google.golang.org/grpc" +) + +type ServiceClient struct { + conn *grpc.ClientConn + client pb.TraceeServiceClient +} + +func NewServiceClient(serverInfo ServerInfo) (*ServiceClient, error) { + conn, err := connectToServer(serverInfo) + if err != nil { + return nil, err + } + return &ServiceClient{ + conn: conn, + client: pb.NewTraceeServiceClient(conn), + }, nil +} +func (tc *ServiceClient) CloseConnection() { + if err := tc.conn.Close(); err != nil { + return + } +} +func (tc *ServiceClient) GetVersion(ctx context.Context, req *pb.GetVersionRequest) (*pb.GetVersionResponse, error) { + return tc.client.GetVersion(ctx, req) +} + +func (tc *ServiceClient) EnableEvent(ctx context.Context, req *pb.EnableEventRequest) (*pb.EnableEventResponse, error) { + return tc.client.EnableEvent(ctx, req) +} + +func (tc *ServiceClient) DisableEvent(ctx context.Context, req *pb.DisableEventRequest) (*pb.DisableEventResponse, error) { + return tc.client.DisableEvent(ctx, req) +} + +func (tc *ServiceClient) StreamEvents(ctx context.Context, req *pb.StreamEventsRequest) (pb.TraceeService_StreamEventsClient, error) { + return tc.client.StreamEvents(ctx, req) +} +func (tc *ServiceClient) GetEventDefinitions(ctx context.Context, req *pb.GetEventDefinitionsRequest) (*pb.GetEventDefinitionsResponse, error) { + return tc.client.GetEventDefinitions(ctx, req) +} diff --git a/cmd/traceectl/pkg/cmd/flags/output.go b/cmd/traceectl/pkg/cmd/flags/output.go new file mode 100644 index 000000000000..193fec0fb787 --- /dev/null +++ b/cmd/traceectl/pkg/cmd/flags/output.go @@ -0,0 +1,36 @@ +package flags + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" +) + +func PrepareOutput(cmd *cobra.Command, output string) error { + if output != "" && output != "stdout" { + if strings.TrimSpace(output) == "" { + return fmt.Errorf("output file path is empty or invalid") + } + + dir := filepath.Dir(output) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directories for output file: %v", err) + } + + file, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("failed to open output file: %v", err) + } + + cmd.SetOut(file) + cmd.SetErr(file) + // Close the file after execution + cmd.PersistentPostRun = func(cmd *cobra.Command, args []string) { + file.Close() + } + } + return nil +} diff --git a/cmd/traceectl/pkg/cmd/flags/server.go b/cmd/traceectl/pkg/cmd/flags/server.go new file mode 100644 index 000000000000..5191eddde1bb --- /dev/null +++ b/cmd/traceectl/pkg/cmd/flags/server.go @@ -0,0 +1,54 @@ +package flags + +import ( + "fmt" + "net" + "strings" + + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/client" + "github.com/spf13/cobra" +) + +func PrepareServer(cmd *cobra.Command, server client.ServerInfo) (client.ServerInfo, error) { + var err error + var address string + err = determineConnectionType(server) + if err != nil { + return server, err + } + switch server.ConnectionType { + case client.Protocol_UNIX: + address = fmt.Sprintf("unix://%s", server.Addr) + case client.Protocol_TCP: + address = fmt.Sprintf(server.Addr) + default: + return server, fmt.Errorf("unsupported connection type: %s", server.ConnectionType) + } + server.Addr = address + return server, nil +} + +func determineConnectionType(server client.ServerInfo) error { + if strings.Contains(server.Addr, ":") && isValidTCPAddress(server.Addr) { + server.ConnectionType = client.Protocol_TCP + return nil + } + if strings.HasPrefix(server.Addr, "/") { + server.ConnectionType = client.Protocol_UNIX + return nil + } + + return fmt.Errorf("unsupported connection type: %s", server.Addr) + +} +func isValidTCPAddress(addr string) bool { + host, port, err := net.SplitHostPort(addr) + if err != nil || host == "" || port == "" { + return false + } + if _, err := net.LookupPort("tcp", port); err != nil { + return false + } + + return true +} diff --git a/cmd/traceectl/pkg/cmd/formatter/formatter.go b/cmd/traceectl/pkg/cmd/formatter/formatter.go new file mode 100644 index 000000000000..77e7ad8137e7 --- /dev/null +++ b/cmd/traceectl/pkg/cmd/formatter/formatter.go @@ -0,0 +1,95 @@ +package formatter + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +const ( + FormatJson = "json" + FormatTable = "table" +) + +var SupportedFormats = []string{FormatJson, FormatTable} + +type Formatter struct { + format string + cmd *cobra.Command + paddings map[int][]int +} + +func NewFormatter(format string, cmd *cobra.Command) (*Formatter, error) { + switch format { + case FormatJson: + return &Formatter{ + format: format, + cmd: cmd, + }, nil + case FormatTable: + //add padding for table + return &Formatter{ + format: format, + cmd: cmd, + paddings: map[int][]int{ + 4: {20, 15, 15, 20}, // Padding for 4 columns + 5: {15, 10, 20, 15, 10}, // Padding for 5 columns + }, + }, nil + default: + return nil, fmt.Errorf("format %s is not supported", format) + } +} +func (f *Formatter) GetFormat() string { + return f.format +} + +// PrintTableHeaders prints table headers with padding based on their length. +func (f *Formatter) PrintTableHeaders(headers []string) { + switch len(headers) { + case 4: + f.cmd.Printf("%-20s %-15s %-15s %-20s\n", + headers[0], + headers[1], + headers[2], + headers[3], + ) + case 5: + f.cmd.Printf("%-15s %-10s %-20s %-15s %-10s\n", + headers[0], + headers[1], + headers[2], + headers[3], + headers[4], + ) + default: + f.cmd.Println("Error: Unsupported number of headers.") + } +} + +// PrintTableRow prints a single row with padding matching the header format. +func (f *Formatter) PrintTableRow(row []string) { + switch len(row) { + case 4: + f.cmd.Printf("%-20s %-15s %-15s %-20s\n", + row[0], + row[1], + row[2], + row[3], + ) + case 5: + f.cmd.Printf("%-15s %-10s %-20s %-15s %-10s\n", + row[0], + row[1], + row[2], + row[3], + row[4], + ) + default: + f.cmd.Println("Error: Unsupported number of columns in row.") + } +} + +func (f *Formatter) PrintJson(data interface{}) { + f.cmd.Println(data) +} diff --git a/cmd/traceectl/pkg/cmd/test/test.go b/cmd/traceectl/pkg/cmd/test/test.go new file mode 100644 index 000000000000..2de066b69ce5 --- /dev/null +++ b/cmd/traceectl/pkg/cmd/test/test.go @@ -0,0 +1,52 @@ +package test + +import ( + "bytes" + "testing" + "time" + + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/mock" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" +) + +type TestCase struct { + TestName string + OutputSlice []string + ExpectedPrinter interface{} + ExpectedError error +} + +func runMockServer(t *testing.T) *grpc.Server { + mockServer, err := mock.StartMockServer() + if err != nil { + t.Fatalf("Failed to start mock server: %v", err) + } + time.Sleep(100 * time.Millisecond) + return mockServer +} +func TestCommand(t *testing.T, testCase TestCase, rootCmd *cobra.Command) { + server := runMockServer(t) + defer server.Stop() + var buf bytes.Buffer + rootCmd.SetOut(&buf) + rootCmd.SetErr(&buf) + rootCmd.SetArgs(testCase.OutputSlice) + err := rootCmd.Execute() + output := buf.String() + if err != nil && testCase.ExpectedError == nil { + t.Errorf("Unexpected error for test %s: %v", testCase.TestName, err) + return + } + if err == nil && testCase.ExpectedError != nil { + t.Errorf("Expected error for test %s but got none", testCase.TestName) + return + } + if testCase.ExpectedError != nil { + assert.ErrorContains(t, err, testCase.ExpectedError.Error()) + } else { + assert.Contains(t, output, testCase.ExpectedPrinter) + + } +} diff --git a/cmd/traceectl/pkg/mock/diagnostic_server.go b/cmd/traceectl/pkg/mock/diagnostic_server.go new file mode 100644 index 000000000000..1d4fcff36e58 --- /dev/null +++ b/cmd/traceectl/pkg/mock/diagnostic_server.go @@ -0,0 +1,17 @@ +package mock + +import ( + "context" + + pb "github.com/aquasecurity/tracee/api/v1beta1" +) + +var ( + ExpectedMetrics pb.GetMetricsResponse = pb.GetMetricsResponse{EventCount: 1, EventsFiltered: 2, NetCapCount: 3, + BPFLogsCount: 4, ErrorCount: 5, LostEvCount: 6, + LostWrCount: 7, LostNtCapCount: 8, LostBPFLogsCount: 9} +) + +func (s *MockDiagnosticServer) GetMetrics(ctx context.Context, req *pb.GetMetricsRequest) (*pb.GetMetricsResponse, error) { + return &ExpectedMetrics, nil +} diff --git a/cmd/traceectl/pkg/mock/event_server.go b/cmd/traceectl/pkg/mock/event_server.go new file mode 100644 index 000000000000..689c98d8554c --- /dev/null +++ b/cmd/traceectl/pkg/mock/event_server.go @@ -0,0 +1,28 @@ +package mock + +import ( + "context" + + pb "github.com/aquasecurity/tracee/api/v1beta1" +) + +func (s *MockServiceServer) GetEventDefinitions(ctx context.Context, req *pb.GetEventDefinitionsRequest) (*pb.GetEventDefinitionsResponse, error) { + var eventDefinition []*pb.EventDefinition + for i := 0; i < len(req.EventNames); i++ { + eventDefinition = append(eventDefinition, &pb.EventDefinition{Name: req.EventNames[i], Id: (int32(i))}) + } + return &pb.GetEventDefinitionsResponse{Definitions: eventDefinition}, nil +} +func (s *MockServiceServer) DescribeEvent(ctx context.Context, req *pb.GetEventDefinitionsRequest) (*pb.GetEventDefinitionsResponse, error) { + return s.GetEventDefinitions(ctx, req) +} +func (s *MockServiceServer) ListEvent(ctx context.Context, req *pb.GetEventDefinitionsRequest) (*pb.GetEventDefinitionsResponse, error) { + return s.GetEventDefinitions(ctx, req) +} + +func (s *MockServiceServer) EnableEvent(ctx context.Context, req *pb.EnableEventRequest) (*pb.EnableEventResponse, error) { + return &pb.EnableEventResponse{}, nil +} +func (s *MockServiceServer) DisableEvent(ctx context.Context, req *pb.DisableEventRequest) (*pb.DisableEventResponse, error) { + return &pb.DisableEventResponse{}, nil +} diff --git a/cmd/traceectl/pkg/mock/root_server.go b/cmd/traceectl/pkg/mock/root_server.go new file mode 100644 index 000000000000..2d9ba32021aa --- /dev/null +++ b/cmd/traceectl/pkg/mock/root_server.go @@ -0,0 +1,11 @@ +package mock + +import ( + "context" + + pb "github.com/aquasecurity/tracee/api/v1beta1" +) + +func (s *MockServiceServer) GetVersion(ctx context.Context, req *pb.GetVersionRequest) (*pb.GetVersionResponse, error) { + return &pb.GetVersionResponse{Version: ExpectedVersion}, nil +} diff --git a/cmd/traceectl/pkg/mock/server.go b/cmd/traceectl/pkg/mock/server.go new file mode 100644 index 000000000000..1353f22e6010 --- /dev/null +++ b/cmd/traceectl/pkg/mock/server.go @@ -0,0 +1,63 @@ +package mock + +import ( + "fmt" + "net" + "os" + + pb "github.com/aquasecurity/tracee/api/v1beta1" + "github.com/aquasecurity/tracee/cmd/traceectl/pkg/client" + + "google.golang.org/grpc" +) + +var ( + ExpectedVersion string = "v0.22.0-15-gd09d7fca0d" + serverInfo *client.ServerInfo = &client.ServerInfo{ + Addr: client.Socket, + ConnectionType: client.Protocol_UNIX, + } +) + +type MockServiceServer struct { + pb.UnimplementedTraceeServiceServer +} +type MockDiagnosticServer struct { + pb.UnimplementedDiagnosticServiceServer +} + +func CreateMockServer() (*grpc.Server, net.Listener, error) { + if _, err := os.Stat(serverInfo.Addr); err == nil { + if err := os.Remove(serverInfo.Addr); err != nil { + return nil, nil, fmt.Errorf("failed to cleanup gRPC listening address (%s): %v", serverInfo.Addr, err) + } + } + listener, err := net.Listen("unix", serverInfo.Addr) + if err != nil { + return nil, nil, fmt.Errorf("failed to create Unix socket listener: %v", err) + } + server := grpc.NewServer() + pb.RegisterTraceeServiceServer(server, &MockServiceServer{}) + pb.RegisterDiagnosticServiceServer(server, &MockDiagnosticServer{}) + + return server, listener, nil +} +func StartMockServer() (*grpc.Server, error) { + mockServer, listener, err := CreateMockServer() + if err != nil { + return nil, fmt.Errorf("failed to create mock server: %v", err) + } + go func() { + if err := mockServer.Serve(listener); err != nil { + fmt.Printf("gRPC server failed: %v\n", err) + } + }() + + return mockServer, nil +} +func StopMockServer(server *grpc.Server) { + server.GracefulStop() + if err := os.Remove(serverInfo.Addr); err != nil { + fmt.Printf("failed to remove Unix socket: %v\n", err) + } +} diff --git a/cmd/traceectl/pkg/mock/stream_server.go b/cmd/traceectl/pkg/mock/stream_server.go new file mode 100644 index 000000000000..e69db107da21 --- /dev/null +++ b/cmd/traceectl/pkg/mock/stream_server.go @@ -0,0 +1,64 @@ +package mock + +import ( + "sort" + "strings" + "time" + + pb "github.com/aquasecurity/tracee/api/v1beta1" +) + +func (s *MockServiceServer) StreamEvents(req *pb.StreamEventsRequest, stream pb.TraceeService_StreamEventsServer) error { + mockEvents := CreateEventsFromPolicies(req.Policies) + for _, event := range mockEvents { + if err := stream.Send(event); err != nil { + return err + } + } + time.Sleep(100 * time.Millisecond) + return nil +} +func generateEvent(policy []string) *pb.Event { + return &pb.Event{ + Policies: &pb.Policies{Matched: policy}, + } +} +func CreateEventsFromPolicies(policies []string) []*pb.StreamEventsResponse { + if len(policies) == 0 { + return []*pb.StreamEventsResponse{ + {Event: generateEvent([]string{""})}, + } + } + sort.Strings(policies) + var results []*pb.StreamEventsResponse + combinations := generateCombinations(policies) + sort.SliceStable(combinations, func(i, j int) bool { + if len(combinations[i]) != len(combinations[j]) { + return len(combinations[i]) < len(combinations[j]) + } + return strings.Join(combinations[i], ",") < strings.Join(combinations[j], ",") + }) + for _, combo := range combinations { + results = append(results, &pb.StreamEventsResponse{ + Event: generateEvent(combo), + }) + } + + return results +} +func generateCombinations(policies []string) [][]string { + var result [][]string + n := len(policies) + var helper func(start int, combo []string) + helper = func(start int, combo []string) { + if len(combo) > 0 { + combinationCopy := append([]string{}, combo...) + result = append(result, combinationCopy) + } + for i := start; i < n; i++ { + helper(i+1, append(combo, policies[i])) + } + } + helper(0, []string{}) + return result +} diff --git a/docs/docs/traceectl/commands/event.md b/docs/docs/traceectl/commands/event.md new file mode 100644 index 000000000000..a566728ff2bf --- /dev/null +++ b/docs/docs/traceectl/commands/event.md @@ -0,0 +1,83 @@ +# Event Command Usage + +The `event` command in **traceectl** is used for managing events within Tracee. It allows you to list, describe, enable, and disable various event types that Tracee can capture. Below is the usage guide for the `event` command and its subcommands. + +## Usage + +The `event` command is structured as follows: + +```sh +traceectl event [subcommand] [flags] +``` + +## Subcommands + +- **list**: Lists all available event definitions (built-in and plugin-defined), providing a brief summary of each. + + ```sh + traceectl event list --format [json|table|template] + ``` + + - **`--format`** (`-f`): Specifies the output format (default is `table`). Supported formats are `json`, `table`, and `template`. + +- **describe**: Retrieves detailed information about a specific event, including its fields, types, and other metadata. + + ```sh + traceectl event describe --format [json|table|template] + ``` + + - **``**: The name of the event to describe. + - **`--format`** (`-f`): Specifies the output format (default is `table`). + +- **enable**: Enables capturing of a specific event type in Tracee. + + ```sh + traceectl event enable + ``` + + - **``**: The name of the event to enable. + +- **disable**: Disables capturing of a specific event type in Tracee. + + ```sh + traceectl event disable + ``` + + - **``**: The name of the event to disable. + +## Flags + +- **`--format`** (`-f`): Available with the `list` and `describe` subcommands. It specifies the format for the output. Supported values are: + - `json`: Outputs event details in JSON format. + - `table`: Outputs event details in a tabular view. + - `template`: Uses a custom template for formatting the output. + +## Examples + +- **List All Events in JSON Format** + + ```sh + traceectl event list --format json + ``` + +- **Describe an Event** + + ```sh + traceectl event describe execve --format table + ``` + +- **Enable an Event** + + ```sh + traceectl event enable execve + ``` + +- **Disable an Event** + + ```sh + traceectl event disable execve + ``` + +## Summary + +The `event` command in traceectl is a powerful tool for managing Tracee's event capabilities. Use the `list`, `describe`, `enable`, and `disable` subcommands to gain detailed insight and control over the events Tracee monitors. diff --git a/docs/docs/traceectl/commands/metrics.md b/docs/docs/traceectl/commands/metrics.md new file mode 100644 index 000000000000..cd46206bb65e --- /dev/null +++ b/docs/docs/traceectl/commands/metrics.md @@ -0,0 +1,23 @@ +# Metrics Command Usage + +The `metrics` command in **traceectl** provides information about Tracee's performance and resource usage metrics. This command is helpful for monitoring how Tracee is functioning in real-time. + +## Usage + +The `metrics` command is structured as follows: + +```sh +traceectl metrics +``` + +## Examples + +- **Display Metrics in Table Format** + + ```sh + traceectl metrics + ``` + +## Summary + +The `metrics` command displays tracee's performance and resource usage metrics. Use this command to monitor Tracee's operational status. diff --git a/docs/docs/traceectl/commands/stream.md b/docs/docs/traceectl/commands/stream.md new file mode 100644 index 000000000000..9f00ecf31357 --- /dev/null +++ b/docs/docs/traceectl/commands/stream.md @@ -0,0 +1,35 @@ +# Stream Command Usage + +The `stream` command in **traceectl** allows users to stream events directly from Tracee in real time. This command provides flexible output formats for better integration and readability. + +## Usage + +The `stream` command is structured as follows: + +```sh +traceectl stream [policies...] [flags] +``` + +## Flags + +- **`--format`** (`-f`): Specifies the format for the output. Supported values are: + - `json`: Outputs event details in JSON format. + - `table`: Outputs event details in a tabular view. + +## Examples + +- **Stream Events in JSON Format** + + ```sh + traceectl stream --format json + ``` + +- **Stream Events in Table Format** + + ```sh + traceectl stream --format table + ``` + +## Summary + +The `stream` command provides a real-time feed of Tracee events, allowing you to monitor system activity as it happens. diff --git a/docs/docs/traceectl/commands/version.md b/docs/docs/traceectl/commands/version.md new file mode 100644 index 000000000000..c360948f8a74 --- /dev/null +++ b/docs/docs/traceectl/commands/version.md @@ -0,0 +1,28 @@ +# Version Command + +The `version` command in **traceectl** provides detailed information about the current version of the tool. This includes the version number, build date, and other relevant metadata. + +## Usage + +To display the version information, use the following command: + +``` bash +traceectl version +``` + +This command will output details such as: + +- **Version Number**: The current version of traceectl. +- **Commit Hash**: The Git commit hash associated with the current build (if applicable). + +### Example Output + +``` bash +v0.22.0-96-gaab269e885 +``` + +### Summary + +- **`traceectl version`**: Displays detailed version information. + +Use this command to verify your version or to gather information for troubleshooting purposes. diff --git a/docs/docs/traceectl/flags/format.md b/docs/docs/traceectl/flags/format.md new file mode 100644 index 000000000000..df05065d7b0a --- /dev/null +++ b/docs/docs/traceectl/flags/format.md @@ -0,0 +1,29 @@ +# `format` Flag + +The `--format` flag in **traceectl** is used to specify the output format for certain commands. Currently, this flag supports the following values for the `stream`, `event list`, and `event describe` commands: + +- **`json`**: Outputs the data in JSON format, which is useful for automated processing or integration with other tools that consume JSON. + + Example: + + ```sh + traceectl event list --format json + ``` + + In this example, the command lists all available events and outputs them in JSON format. + +- **`table`**: Outputs the data in a tabular format, which is easier to read for users viewing the output directly in the terminal. + + Example: + + ```sh + traceectl stream --format table + ``` + + In this example, the command streams events from Tracee and displays them in a table format, making it more human-readable. + +The `--format` flag is helpful for customizing the output to meet different requirements, whether for readability or integration with other tools. + +## Default Format + +The default format for the `--format` flag is **table**. If no format is specified, the output will be displayed in a tabular format, which is more human-readable for most users. diff --git a/docs/docs/traceectl/flags/output.md b/docs/docs/traceectl/flags/output.md new file mode 100644 index 000000000000..59125ffab997 --- /dev/null +++ b/docs/docs/traceectl/flags/output.md @@ -0,0 +1,23 @@ +# `output` Flag + +The `--output` flag is used to specify the destination for the command's output. This flag can be set to **stdout** or a file location. + +- **stdout**: This is the default output destination, which means that the command's output will be displayed on the terminal. This is convenient for users who want to see immediate results directly in their console. + + Example: + + ```sh + traceectl stream --output stdout + ``` + + In this example, the command outputs the streamed events to the terminal. + +- **File Output**: You can use the `--output` flag to direct the output to a specific file. This is useful if you want to save the output for later analysis or for documentation purposes. + + Example: + + ```sh + traceectl stream --output file:/path/to/output.txt + ``` + + In this example, the command saves the streamed events to the file located at `/path/to/output.txt`. This is especially helpful for logging purposes or when working with large amounts of data that need to be stored for further processing. diff --git a/docs/docs/traceectl/flags/server.md b/docs/docs/traceectl/flags/server.md new file mode 100644 index 000000000000..d11c6a72153f --- /dev/null +++ b/docs/docs/traceectl/flags/server.md @@ -0,0 +1,23 @@ +# `server` Flag + +The `--server` flag in **traceectl** is used to specify the connection type that traceectl should use to communicate with the Tracee server. This connection type can be either **Unix socket** or **TCP**. + +- **Unix Socket**: This type of connection is generally used for local inter-process communication. It provides a secure and efficient means to connect to Tracee when both client and server are on the same machine. + + Example: + + ```sh + traceectl --server unix:/unix/socket/path.sock + ``` + + In this example, `unix:/unix/socket/path.sock` is the Unix socket path where the Tracee server is listening. Using Unix sockets is beneficial for security and performance since it avoids the overhead associated with network communication. + +- **TCP**: This type of connection allows traceectl to communicate with the Tracee server over a network. It is useful when traceectl and Tracee are running on different machines or when you need to connect to a remote Tracee instance. + + Example: + + ```sh + traceectl --server tcp:4466 + ``` + + In this example, `tcp:4466` is the address and port of the Tracee server. This is a typical setup for remote monitoring or when the server and client need to be distributed across different hosts. diff --git a/docs/docs/traceectl/index.md b/docs/docs/traceectl/index.md new file mode 100644 index 000000000000..02a830ca86be --- /dev/null +++ b/docs/docs/traceectl/index.md @@ -0,0 +1,15 @@ +# traceectl - Client Service for Tracee + +**traceectl** is a command-line interface (CLI) tool designed as a client service for [Tracee](https://github.com/aquasecurity/tracee), Aqua Security's open-source runtime security solution. Tracee provides real-time, powerful observability for Linux environments by monitoring system calls, events, and more. traceectl is built to simplify interactions with Tracee, making it easier for users to manage, monitor, and gather security insights. + +## Overview + +traceectl acts as a controller for Tracee, allowing users to: + +- **Stream Events**: Continuously stream security events from Tracee, with options to format the output as JSON, tables, or custom templates. +- **List Available Events**: Display the available events that Tracee can capture, providing essential insights into runtime activities. +- **Query Metrics**: Access various metrics related to Tracee, including event counts, errors, and more. + +## Installation and Usage + +To get started with traceectl, go over the [Installation and Usage page](./usage.md) diff --git a/docs/docs/traceectl/usage.md b/docs/docs/traceectl/usage.md new file mode 100644 index 000000000000..006838f41ea8 --- /dev/null +++ b/docs/docs/traceectl/usage.md @@ -0,0 +1,90 @@ +# traceectl Installation and Usage Guide + +## Installation + +To use **traceectl**, you first need to compile and install the tool. Follow these steps to get started: + +### 1. **Clone the Repository** + + Begin by cloning the Tracee repository to your local machine and navigating to traceectl: + + ``` bash + git clone https://github.com/aquasecurity/tracee.git + cd cmd/traceectl + ``` + +### 2. **Build and Install** + + Compile and install traceectl using the following commands: + + ``` bash + go build + ``` + +## Configuring Tracee for traceectl + +To use traceectl effectively, you need to configure Tracee so that it can communicate with traceectl over a Unix socket. This can be done by running Tracee with the correct gRPC settings: + +### 1. **Run Tracee with gRPC Unix Socket** + +Use the following command to start Tracee with gRPC support over a Unix socket: + +``` bash +tracee --grpc-listen-addr unix:/var/run/tracee.sock +``` + +This command sets up Tracee to listen for incoming connections from traceectl at the specified Unix socket path (`/var/run/tracee.sock`). Ensure that this socket path is accessible and not blocked by permissions or other constraints. + +### 2. **Output Flag Configuration** + +The `--output` flag in Tracee allows you to configure how data from Tracee is presented. Among the available options, you can specify `none` for minimal output, which can be useful for scenarios where bandwidth or latency considerations are critical. For example: + +``` bash +tracee --output none +``` + +#### Why Use `--output none`? + +- **Reduced Bandwidth Usage:** By suppressing output, you can minimize the data transferred over the Unix socket, which is especially helpful in environments with limited resources. +- **Lower Latency:** With no data formatting or transmission overhead, the interaction between traceectl and Tracee becomes faster. + +Use this mode for performance testing, silent monitoring, or when integrating traceectl with other systems that handle data processing separately. + +This command sets up Tracee to listen for incoming connections from traceectl at the specified Unix socket path (`/var/run/tracee.sock`). +Ensure that this socket path is accessible and not blocked by permissions or other constraints. + +## Usage + +Once traceectl is installed and Tracee is running, you can use various commands to interact with Tracee. Below are the main commands provided by traceectl: + +- Stream Events: traceectl stream + +- Events management: traceectl event + +- Retrieve Metrics: traceectl metrics + +- Check Version: traceectl version + +For more info about the traceectl command please refer to the appoint command documentation + +## Flags + +- server: Specifies the connection type, either unix or tcp. + + ``` bash + traceectl --server unix:/unix/socket/path.sock + ``` + +- output: Defines the output destination, such as stdout or a file. + + ``` bash + traceectl stream --output file:/path/to/output.txt + ``` + +For more info about the traceectl flags please refer to the appoint flag documentation + +## Summary + +- **Install traceectl** by cloning the repository, building, and installing it with `make`. +- **Configure Tracee** by running it with the appropriate gRPC Unix socket settings. +- **Use traceectl** to interact with Tracee via commands like `stream`, `event`, `metrics`, and `version`. diff --git a/mkdocs.yml b/mkdocs.yml index d3d2e1692123..1a8290d05efa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -576,6 +576,18 @@ nav: - Output Formats: docs/outputs/output-formats.md - Output Options: docs/outputs/output-options.md - Logging: docs/outputs/logging.md + - TraceeCTL: + - Overview: docs/traceectl/index.md + - Installation: docs/traceectl/usage.md + - Commands: + - event: docs/traceectl/commands/event.md + - metrics: docs/traceectl/commands/metrics.md + - stream: docs/traceectl/commands/stream.md + - verion: docs/traceectl/commands/verion.md + - Flags: + - output: docs/traceectl/flags/output.md + - format: docs/traceectl/flags/format.md + - server: docs/traceectl/flags/server.md - Advanced: - Caching Events: docs/advanced/caching-events.md - Ordering Events: docs/advanced/ordering-events.md