diff --git a/cmd/common.go b/cmd/common.go index deea646a4..f714ae3d8 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -22,7 +22,7 @@ import ( ) func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, ctx context.Context, cancel context.CancelFunc, serviceName string, srcPort uint16, dstPort uint16, healthCheck string) { - httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, srcPort, dstPort, config.Config.ResourcesNamespace, serviceName, cancel) + httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Deploy.ProxyHost, srcPort, dstPort, config.Config.ResourcesNamespace, serviceName, cancel) if err != nil { log.Error(). Err(errormessage.FormatError(err)). @@ -115,7 +115,7 @@ func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provid } } -func getSerializedTapConfig(conf *models.Config) (string, error) { +func getSerializedDeployConfig(conf *models.Config) (string, error) { serializedConfig, err := json.Marshal(conf) if err != nil { return "", err diff --git a/cmd/deploy.go b/cmd/deploy.go new file mode 100644 index 000000000..f393092fe --- /dev/null +++ b/cmd/deploy.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "errors" + + "github.com/creasty/defaults" + "github.com/kubeshark/kubeshark/config" + "github.com/kubeshark/kubeshark/config/configStructs" + "github.com/kubeshark/kubeshark/errormessage" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var deployCmd = &cobra.Command{ + Use: "deploy [POD REGEX]", + Short: "Deploy Kubeshark into your K8s cluster.", + Long: `Deploy Kubeshark into your K8s cluster to gain visibility.`, + RunE: func(cmd *cobra.Command, args []string) error { + RunKubesharkTap() + return nil + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 1 { + config.Config.Deploy.PodRegexStr = args[0] + } else if len(args) > 1 { + return errors.New("unexpected number of arguments") + } + + if err := config.Config.Deploy.Validate(); err != nil { + return errormessage.FormatError(err) + } + + log.Info(). + Str("limit", config.Config.Deploy.HumanMaxEntriesDBSize). + Msg("Kubeshark will store the traffic up to a limit. Oldest entries will be removed once the limit is reached.") + + return nil + }, +} + +func init() { + rootCmd.AddCommand(deployCmd) + + defaultDeployConfig := configStructs.DeployConfig{} + if err := defaults.Set(&defaultDeployConfig); err != nil { + log.Debug().Err(err).Send() + } + + deployCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultDeployConfig.GuiPort, "Provide a custom port for the web interface webserver") + deployCmd.Flags().StringSliceP(configStructs.NamespacesTapName, "n", defaultDeployConfig.Namespaces, "Namespaces selector") + deployCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultDeployConfig.AllNamespaces, "Tap all namespaces") + deployCmd.Flags().Bool(configStructs.EnableRedactionTapName, defaultDeployConfig.EnableRedaction, "Enables redaction of potentially sensitive request/response headers and body values") + deployCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultDeployConfig.HumanMaxEntriesDBSize, "Override the default max entries db size") + deployCmd.Flags().String(configStructs.InsertionFilterName, defaultDeployConfig.InsertionFilter, "Set the insertion filter. Accepts string or a file path.") + deployCmd.Flags().Bool(configStructs.DryRunTapName, defaultDeployConfig.DryRun, "Preview of all pods matching the regex, without tapping them") + deployCmd.Flags().Bool(configStructs.ServiceMeshName, defaultDeployConfig.ServiceMesh, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls") + deployCmd.Flags().Bool(configStructs.TlsName, defaultDeployConfig.Tls, "Record tls traffic") + deployCmd.Flags().Bool(configStructs.ProfilerName, defaultDeployConfig.Profiler, "Run pprof server") + deployCmd.Flags().Int(configStructs.MaxLiveStreamsName, defaultDeployConfig.MaxLiveStreams, "Maximum live tcp streams to handle concurrently") +} diff --git a/cmd/tapRunner.go b/cmd/deployRunner.go similarity index 92% rename from cmd/tapRunner.go rename to cmd/deployRunner.go index 600050002..e6e370a73 100644 --- a/cmd/tapRunner.go +++ b/cmd/deployRunner.go @@ -54,8 +54,8 @@ func RunKubesharkTap() { state.targetNamespaces = getNamespaces(kubernetesProvider) - conf := getTapConfig() - serializedKubesharkConfig, err := getSerializedTapConfig(conf) + conf := getDeployConfig() + serializedKubesharkConfig, err := getSerializedDeployConfig(conf) if err != nil { log.Error().Err(errormessage.FormatError(err)).Msg("Error serializing Kubeshark config!") return @@ -74,12 +74,12 @@ func RunKubesharkTap() { log.Error().Err(errormessage.FormatError(err)).Msg("Error listing pods!") } - if config.Config.Tap.DryRun { + if config.Config.Deploy.DryRun { return } log.Info().Msg("Waiting for Kubeshark deployment to finish...") - if state.kubesharkServiceAccountExists, err = resources.CreateTapKubesharkResources(ctx, kubernetesProvider, serializedKubesharkConfig, config.Config.IsNsRestrictedMode(), config.Config.ResourcesNamespace, config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.HubResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), config.Config.Tap.Profiler); err != nil { + if state.kubesharkServiceAccountExists, err = resources.CreateTapKubesharkResources(ctx, kubernetesProvider, serializedKubesharkConfig, config.Config.IsNsRestrictedMode(), config.Config.ResourcesNamespace, config.Config.Deploy.MaxEntriesDBSizeBytes(), config.Config.Deploy.HubResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), config.Config.Deploy.Profiler); err != nil { var statusError *k8serrors.StatusError if errors.As(err, &statusError) && (statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists) { log.Info().Msg("Kubeshark is already running in this namespace, change the `kubeshark-resources-namespace` configuration or run `kubeshark clean` to remove the currently running Kubeshark instance") @@ -105,12 +105,12 @@ func finishTapExecution(kubernetesProvider *kubernetes.Provider) { finishKubesharkExecution(kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.ResourcesNamespace) } -func getTapConfig() *models.Config { +func getDeployConfig() *models.Config { conf := models.Config{ - MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(), - InsertionFilter: config.Config.Tap.GetInsertionFilter(), + MaxDBSizeBytes: config.Config.Deploy.MaxEntriesDBSizeBytes(), + InsertionFilter: config.Config.Deploy.GetInsertionFilter(), PullPolicy: config.Config.ImagePullPolicyStr, - TapperResources: config.Config.Tap.TapperResources, + TapperResources: config.Config.Deploy.TapperResources, KubesharkResourcesNamespace: config.Config.ResourcesNamespace, AgentDatabasePath: models.DataDirPath, ServiceMap: config.Config.ServiceMap, @@ -126,7 +126,7 @@ The alternative would be to wait for Hub to be ready and then query it for the p the arguably worse drawback of taking a relatively very long time before the user sees which pods are targeted, if any. */ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes.Provider, namespaces []string) error { - if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, config.Config.Tap.PodRegex(), namespaces); err != nil { + if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, config.Config.Deploy.PodRegex(), namespaces); err != nil { return err } else { if len(matchingPods) == 0 { @@ -142,18 +142,18 @@ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes. func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, startTime time.Time) error { tapperSyncer, err := kubernetes.CreateAndStartKubesharkTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{ TargetNamespaces: targetNamespaces, - PodFilterRegex: *config.Config.Tap.PodRegex(), + PodFilterRegex: *config.Config.Deploy.PodRegex(), KubesharkResourcesNamespace: config.Config.ResourcesNamespace, - TapperResources: config.Config.Tap.TapperResources, + TapperResources: config.Config.Deploy.TapperResources, ImagePullPolicy: config.Config.ImagePullPolicy(), LogLevel: config.Config.LogLevel(), KubesharkApiFilteringOptions: api.TrafficFilteringOptions{ - IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents, + IgnoredUserAgents: config.Config.Deploy.IgnoredUserAgents, }, KubesharkServiceAccountExists: state.kubesharkServiceAccountExists, - ServiceMesh: config.Config.Tap.ServiceMesh, - Tls: config.Config.Tap.Tls, - MaxLiveStreams: config.Config.Tap.MaxLiveStreams, + ServiceMesh: config.Config.Deploy.ServiceMesh, + Tls: config.Config.Deploy.Tls, + MaxLiveStreams: config.Config.Deploy.MaxLiveStreams, }, startTime) if err != nil { @@ -471,10 +471,10 @@ func postFrontStarted(ctx context.Context, kubernetesProvider *kubernetes.Provid } func getNamespaces(kubernetesProvider *kubernetes.Provider) []string { - if config.Config.Tap.AllNamespaces { + if config.Config.Deploy.AllNamespaces { return []string{kubernetes.K8sAllNamespaces} - } else if len(config.Config.Tap.Namespaces) > 0 { - return utils.Unique(config.Config.Tap.Namespaces) + } else if len(config.Config.Deploy.Namespaces) > 0 { + return utils.Unique(config.Config.Deploy.Namespaces) } else { currentNamespace, err := kubernetesProvider.CurrentNamespace() if err != nil { diff --git a/cmd/open.go b/cmd/open.go index b56ba2a77..0abb798b4 100644 --- a/cmd/open.go +++ b/cmd/open.go @@ -6,7 +6,7 @@ import ( var openCmd = &cobra.Command{ Use: "open", - Short: "Open the web UI in the browser", + Short: "Open the web UI in the browser.", RunE: func(cmd *cobra.Command, args []string) error { runOpen() return nil diff --git a/cmd/root.go b/cmd/root.go index bebd73b07..5f793d49d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,9 +11,10 @@ import ( var rootCmd = &cobra.Command{ Use: "kubeshark", - Short: "A web traffic viewer for kubernetes", - Long: `A web traffic viewer for kubernetes -Further info is available at https://github.com/kubeshark/kubeshark`, + Short: "Kubeshark: The Observability and Monitoring Tool For Kubernetes", + Long: `Kubeshark: The Observability and Monitoring Tool For Kubernetes +An extensible Kubernetes-aware network sniffer and kernel tracer. +For more info: https://kubeshark.co`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := config.InitConfig(cmd); err != nil { log.Fatal().Err(err).Send() diff --git a/cmd/tap.go b/cmd/tap.go deleted file mode 100644 index 7527b0fb6..000000000 --- a/cmd/tap.go +++ /dev/null @@ -1,61 +0,0 @@ -package cmd - -import ( - "errors" - - "github.com/creasty/defaults" - "github.com/kubeshark/kubeshark/config" - "github.com/kubeshark/kubeshark/config/configStructs" - "github.com/kubeshark/kubeshark/errormessage" - "github.com/rs/zerolog/log" - "github.com/spf13/cobra" -) - -var tapCmd = &cobra.Command{ - Use: "tap [POD REGEX]", - Short: "Record ingoing traffic of a kubernetes pod", - Long: `Record the ingoing traffic of a kubernetes pod. -Supported protocols are HTTP and gRPC.`, - RunE: func(cmd *cobra.Command, args []string) error { - RunKubesharkTap() - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 1 { - config.Config.Tap.PodRegexStr = args[0] - } else if len(args) > 1 { - return errors.New("unexpected number of arguments") - } - - if err := config.Config.Tap.Validate(); err != nil { - return errormessage.FormatError(err) - } - - log.Info(). - Str("limit", config.Config.Tap.HumanMaxEntriesDBSize). - Msg("Kubeshark will store the traffic up to a limit. Oldest entries will be removed once the limit is reached.") - - return nil - }, -} - -func init() { - rootCmd.AddCommand(tapCmd) - - defaultTapConfig := configStructs.TapConfig{} - if err := defaults.Set(&defaultTapConfig); err != nil { - log.Debug().Err(err).Send() - } - - tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver") - tapCmd.Flags().StringSliceP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector") - tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces") - tapCmd.Flags().Bool(configStructs.EnableRedactionTapName, defaultTapConfig.EnableRedaction, "Enables redaction of potentially sensitive request/response headers and body values") - tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size") - tapCmd.Flags().String(configStructs.InsertionFilterName, defaultTapConfig.InsertionFilter, "Set the insertion filter. Accepts string or a file path.") - tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them") - tapCmd.Flags().Bool(configStructs.ServiceMeshName, defaultTapConfig.ServiceMesh, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls") - tapCmd.Flags().Bool(configStructs.TlsName, defaultTapConfig.Tls, "Record tls traffic") - tapCmd.Flags().Bool(configStructs.ProfilerName, defaultTapConfig.Profiler, "Run pprof server") - tapCmd.Flags().Int(configStructs.MaxLiveStreamsName, defaultTapConfig.MaxLiveStreams, "Maximum live tcp streams to handle concurrently") -} diff --git a/config/configStruct.go b/config/configStruct.go index c6f84cd42..3cc835795 100644 --- a/config/configStruct.go +++ b/config/configStruct.go @@ -57,7 +57,7 @@ func CreateDefaultConfig() ConfigStruct { type ConfigStruct struct { Hub HubConfig `yaml:"hub"` Front FrontConfig `yaml:"front"` - Tap configStructs.TapConfig `yaml:"tap"` + Deploy configStructs.DeployConfig `yaml:"deploy"` Logs configStructs.LogsConfig `yaml:"logs"` Config configStructs.ConfigConfig `yaml:"config,omitempty"` ImagePullPolicyStr string `yaml:"image-pull-policy" default:"Always"` diff --git a/config/configStructs/tapConfig.go b/config/configStructs/tapConfig.go index 0e9fd6d16..33a763914 100644 --- a/config/configStructs/tapConfig.go +++ b/config/configStructs/tapConfig.go @@ -26,7 +26,7 @@ const ( MaxLiveStreamsName = "max-live-streams" ) -type TapConfig struct { +type DeployConfig struct { PodRegexStr string `yaml:"regex" default:".*"` GuiPort uint16 `yaml:"gui-port" default:"8899"` ProxyHost string `yaml:"proxy-host" default:"127.0.0.1"` @@ -53,17 +53,17 @@ type TapConfig struct { MaxLiveStreams int `yaml:"max-live-streams" default:"500"` } -func (config *TapConfig) PodRegex() *regexp.Regexp { +func (config *DeployConfig) PodRegex() *regexp.Regexp { podRegex, _ := regexp.Compile(config.PodRegexStr) return podRegex } -func (config *TapConfig) MaxEntriesDBSizeBytes() int64 { +func (config *DeployConfig) MaxEntriesDBSizeBytes() int64 { maxEntriesDBSizeBytes, _ := utils.HumanReadableToBytes(config.HumanMaxEntriesDBSize) return maxEntriesDBSizeBytes } -func (config *TapConfig) GetInsertionFilter() string { +func (config *DeployConfig) GetInsertionFilter() string { insertionFilter := config.InsertionFilter if fs.ValidPath(insertionFilter) { if _, err := os.Stat(insertionFilter); err == nil { @@ -87,7 +87,7 @@ func (config *TapConfig) GetInsertionFilter() string { return insertionFilter } -func getRedactFilter(config *TapConfig) string { +func getRedactFilter(config *DeployConfig) string { if !config.EnableRedaction { return "" } @@ -118,7 +118,7 @@ func getRedactFilter(config *TapConfig) string { return fmt.Sprintf("redact(\"%s\")", strings.Join(redactValues, "\",\"")) } -func (config *TapConfig) Validate() error { +func (config *DeployConfig) Validate() error { _, compileErr := regexp.Compile(config.PodRegexStr) if compileErr != nil { return fmt.Errorf("%s is not a valid regex %s", config.PodRegexStr, compileErr)