From 04c0f8cbcd602ef6c52dab420fb9bd5e9b9cfc54 Mon Sep 17 00:00:00 2001 From: RoyUP9 <87927115+RoyUP9@users.noreply.github.com> Date: Mon, 11 Oct 2021 15:42:41 +0300 Subject: [PATCH] tap to workspace (#315) --- cli/apiserver/provider.go | 4 +- cli/auth/authProvider.go | 17 ++++---- cli/cmd/tap.go | 61 +++++++++++++++++++++------ cli/cmd/tapRunner.go | 5 ++- cli/config/configStructs/tapConfig.go | 14 +++++- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/cli/apiserver/provider.go b/cli/apiserver/provider.go index 5bd3e6771..4be8ea2f2 100644 --- a/cli/apiserver/provider.go +++ b/cli/apiserver/provider.go @@ -82,11 +82,11 @@ func (provider *apiServerProvider) ReportTappedPods(pods []core.Pod) error { } } -func (provider *apiServerProvider) RequestSyncEntries(analysisDestination string, sleepIntervalSec int) error { +func (provider *apiServerProvider) RequestSyncEntries(envName string, workspace string, uploadIntervalSec int, token string) error { if !provider.isReady { return fmt.Errorf("trying to reach api server when not initialized yet") } - urlPath := fmt.Sprintf("%s/api/syncEntries?env=%s&interval=%v", provider.url, url.QueryEscape(analysisDestination), sleepIntervalSec) + urlPath := fmt.Sprintf("%s/api/syncEntries?env=%s&workspace=%s&token=%s&interval=%v", provider.url, url.QueryEscape(envName), url.QueryEscape(workspace), url.QueryEscape(token), uploadIntervalSec) syncEntriesUrl, parseErr := url.ParseRequestURI(urlPath) if parseErr != nil { logger.Log.Fatal("Failed parsing the URL (consider changing the env name), err: %v", parseErr) diff --git a/cli/auth/authProvider.go b/cli/auth/authProvider.go index ab7766dfc..5a9b89a92 100644 --- a/cli/auth/authProvider.go +++ b/cli/auth/authProvider.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/creasty/defaults" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/up9inc/mizu/cli/config" @@ -40,9 +39,9 @@ func IsTokenExpired(tokenString string) (bool, error) { } func Login() error { - token, err := loginInteractively() - if err != nil { - return fmt.Errorf("failed login interactively, err: %v", err) + token, loginErr := loginInteractively() + if loginErr != nil { + return fmt.Errorf("failed login interactively, err: %v", loginErr) } authConfig := configStructs.AuthConfig{ @@ -50,18 +49,18 @@ func Login() error { Token: token.AccessToken, } - configFile := config.ConfigStruct{} - if err := defaults.Set(&configFile); err != nil { - return fmt.Errorf("failed inserting default values to config, err: %v", err) + configFile, defaultConfigErr := config.GetConfigWithDefaults() + if defaultConfigErr != nil { + return fmt.Errorf("failed getting config with defaults, err: %v", defaultConfigErr) } - if err := config.LoadConfigFile(config.Config.ConfigFilePath, &configFile); err != nil && !os.IsNotExist(err) { + if err := config.LoadConfigFile(config.Config.ConfigFilePath, configFile); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed getting config file, err: %v", err) } configFile.Auth = authConfig - if err := config.WriteConfig(&configFile); err != nil { + if err := config.WriteConfig(configFile); err != nil { return fmt.Errorf("failed writing config with auth, err: %v", err) } diff --git a/cli/cmd/tap.go b/cli/cmd/tap.go index b7eb75613..6984cea2a 100644 --- a/cli/cmd/tap.go +++ b/cli/cmd/tap.go @@ -2,20 +2,20 @@ package cmd import ( "errors" - "os" - - "github.com/up9inc/mizu/cli/config" - "github.com/up9inc/mizu/cli/config/configStructs" - "github.com/up9inc/mizu/cli/logger" - "github.com/up9inc/mizu/cli/telemetry" - + "fmt" "github.com/creasty/defaults" "github.com/spf13/cobra" + "github.com/up9inc/mizu/cli/auth" + "github.com/up9inc/mizu/cli/config" + "github.com/up9inc/mizu/cli/config/configStructs" "github.com/up9inc/mizu/cli/errormessage" + "github.com/up9inc/mizu/cli/logger" + "github.com/up9inc/mizu/cli/telemetry" "github.com/up9inc/mizu/cli/uiUtils" + "os" ) -const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.` +const uploadTrafficMessageToConfirm = `NOTE: running mizu with --%s flag will upload recorded traffic for further analysis and enriched presentation options.` var tapCmd = &cobra.Command{ Use: "tap [POD REGEX]", @@ -38,20 +38,52 @@ Supported protocols are HTTP and gRPC.`, return errormessage.FormatError(err) } - logger.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", config.Config.Tap.HumanMaxEntriesDBSize) + if config.Config.Tap.Workspace != "" { + askConfirmation(configStructs.WorkspaceTapName) - if config.Config.Tap.Analysis { - logger.Log.Infof(analysisMessageToConfirm) - if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") { - logger.Log.Infof("You can always run mizu without analysis, aborting") - os.Exit(0) + if config.Config.Auth.Token == "" { + logger.Log.Infof("This action requires authentication, please log in to continue") + if err := auth.Login(); err != nil { + logger.Log.Errorf("failed to log in, err: %v", err) + return nil + } + } else { + tokenExpired, err := auth.IsTokenExpired(config.Config.Auth.Token) + if err != nil { + logger.Log.Errorf("failed to check if token is expired, err: %v", err) + return nil + } + + if tokenExpired { + logger.Log.Infof("Token expired, please log in again to continue") + if err := auth.Login(); err != nil { + logger.Log.Errorf("failed to log in, err: %v", err) + return nil + } + } } } + if config.Config.Tap.Analysis { + askConfirmation(configStructs.AnalysisTapName) + + config.Config.Auth.Token = "" + } + + logger.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", config.Config.Tap.HumanMaxEntriesDBSize) + return nil }, } +func askConfirmation(flagName string) { + logger.Log.Infof(fmt.Sprintf(uploadTrafficMessageToConfirm, flagName)) + if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") { + logger.Log.Infof("You can always run mizu without %s, aborting", flagName) + os.Exit(0) + } +} + func init() { rootCmd.AddCommand(tapCmd) @@ -66,5 +98,6 @@ func init() { tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables 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().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them") + tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)") tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules") } diff --git a/cli/cmd/tapRunner.go b/cli/cmd/tapRunner.go index f5e6bd95c..2e971facb 100644 --- a/cli/cmd/tapRunner.go +++ b/cli/cmd/tapRunner.go @@ -672,10 +672,11 @@ func watchTapperPod(ctx context.Context, kubernetesProvider *kubernetes.Provider } func requestForSyncEntriesIfNeeded() { - if !config.Config.Tap.Analysis { + if !config.Config.Tap.Analysis && config.Config.Tap.Workspace == "" { return } - if err := apiserver.Provider.RequestSyncEntries(config.Config.Tap.AnalysisDestination, config.Config.Tap.UploadIntervalSec); err != nil { + + if err := apiserver.Provider.RequestSyncEntries(config.Config.Auth.EnvName, config.Config.Tap.Workspace, config.Config.Tap.UploadIntervalSec, config.Config.Auth.Token); err != nil { logger.Log.Debugf("[Error] failed requesting for sync entries, err: %v", err) } } diff --git a/cli/config/configStructs/tapConfig.go b/cli/config/configStructs/tapConfig.go index eda9674aa..7e6aca356 100644 --- a/cli/config/configStructs/tapConfig.go +++ b/cli/config/configStructs/tapConfig.go @@ -16,11 +16,11 @@ const ( DisableRedactionTapName = "no-redact" HumanMaxEntriesDBSizeTapName = "max-entries-db-size" DryRunTapName = "dry-run" + WorkspaceTapName = "workspace" EnforcePolicyFile = "traffic-validation-file" ) type TapConfig struct { - AnalysisDestination string `yaml:"dest" default:"up9.app"` UploadIntervalSec int `yaml:"upload-interval" default:"10"` PodRegexStr string `yaml:"regex" default:".*"` GuiPort uint16 `yaml:"gui-port" default:"8899"` @@ -32,6 +32,7 @@ type TapConfig struct { DisableRedaction bool `yaml:"no-redact" default:"false"` HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"` DryRun bool `yaml:"dry-run" default:"false"` + Workspace string `yaml:"workspace"` EnforcePolicyFile string `yaml:"traffic-validation-file"` ApiServerResources Resources `yaml:"api-server-resources"` TapperResources Resources `yaml:"tapper-resources"` @@ -65,5 +66,16 @@ func (config *TapConfig) Validate() error { return errors.New(fmt.Sprintf("Could not parse --%s value %s", HumanMaxEntriesDBSizeTapName, config.HumanMaxEntriesDBSize)) } + if config.Workspace != "" { + workspaceRegex, _ := regexp.Compile("[A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]+$") + if len(config.Workspace) > 63 || !workspaceRegex.MatchString(config.Workspace) { + return errors.New("invalid workspace name") + } + } + + if config.Analysis && config.Workspace != "" { + return errors.New(fmt.Sprintf("Can't run with both --%s and --%s flags", AnalysisTapName, WorkspaceTapName)) + } + return nil }