From 9b65cdfb5bbe6966c4fb05953ab85ea2bc2adf64 Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Thu, 12 Dec 2024 17:03:03 +1000 Subject: [PATCH] Update interactive config wizard (#3964) Use /~https://github.com/charmbracelet/huh instead of the deprecated github.com/AlecAivazis/survey. Refactor the questions to make the process more streamlined. --- bin/config.go | 89 +-- bin/config_frontend.go | 189 ----- bin/config_interactive.go | 673 ------------------ bin/gui.go | 3 +- bin/main.go | 8 +- go.mod | 22 +- go.sum | 52 +- .../src/components/vfs/file-list.jsx | 16 +- services/indexing/index.go | 12 +- tools/survey/README.md | 96 +++ tools/survey/allowlist.go | 194 +++++ tools/survey/api_config.go | 35 + tools/survey/autocert.go | 19 + tools/survey/compile.go | 156 ++++ tools/survey/frontend.go | 212 ++++++ tools/survey/keys.go | 59 ++ tools/survey/network.go | 94 +++ tools/survey/self_signed.go | 19 + tools/survey/server.go | 96 +++ tools/survey/sso.go | 136 ++++ tools/survey/storage.go | 160 +++++ tools/survey/survey.go | 94 +++ 22 files changed, 1479 insertions(+), 955 deletions(-) delete mode 100644 bin/config_frontend.go delete mode 100644 bin/config_interactive.go create mode 100644 tools/survey/README.md create mode 100644 tools/survey/allowlist.go create mode 100644 tools/survey/api_config.go create mode 100644 tools/survey/autocert.go create mode 100644 tools/survey/compile.go create mode 100644 tools/survey/frontend.go create mode 100644 tools/survey/keys.go create mode 100644 tools/survey/network.go create mode 100644 tools/survey/self_signed.go create mode 100644 tools/survey/server.go create mode 100644 tools/survey/sso.go create mode 100644 tools/survey/storage.go create mode 100644 tools/survey/survey.go diff --git a/bin/config.go b/bin/config.go index 4fc1d2e6b01..1745be17d76 100644 --- a/bin/config.go +++ b/bin/config.go @@ -20,13 +20,11 @@ package main import ( "crypto/rand" "crypto/x509" - "encoding/base64" "encoding/pem" "fmt" "os" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/Velocidex/yaml/v2" errors "github.com/go-errors/errors" "software.sslmate.com/src/go-pkcs12" @@ -41,6 +39,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/users" "www.velocidex.com/golang/velociraptor/startup" + vsurvey "www.velocidex.com/golang/velociraptor/tools/survey" "www.velocidex.com/golang/velociraptor/utils" ) @@ -121,6 +120,9 @@ var ( config_reissue_server_key_validity = config_reissue_server_key.Flag( "validity", "How long should the new certs be valid for in days (default 365).").Int64() + + config_frontend_command = config_command.Command( + "frontend", "Experimental: Create multi-frontend configuration") ) func maybeGetOrgConfig( @@ -195,54 +197,6 @@ func doShowConfig() error { return nil } -func generateNewKeys(config_obj *config_proto.Config) error { - ca_bundle, err := crypto.GenerateCACert(2048) - if err != nil { - return fmt.Errorf("Unable to create CA cert: %w", err) - } - - config_obj.Client.CaCertificate = ca_bundle.Cert - config_obj.CA.PrivateKey = ca_bundle.PrivateKey - - nonce := make([]byte, 8) - _, err = rand.Read(nonce) - if err != nil { - return fmt.Errorf("Unable to create nonce: %w", err) - } - config_obj.Client.Nonce = base64.StdEncoding.EncodeToString(nonce) - - // Make another nonce for VQL obfuscation. - _, err = rand.Read(nonce) - if err != nil { - return fmt.Errorf("Unable to create nonce: %w", err) - } - config_obj.ObfuscationNonce = base64.StdEncoding.EncodeToString(nonce) - - // Generate frontend certificate. Frontend certificates must - // have a constant common name - clients will refuse to talk - // with another common name. - frontend_cert, err := crypto.GenerateServerCert( - config_obj, utils.GetSuperuserName(config_obj)) - if err != nil { - return fmt.Errorf("Unable to create Frontend cert: %w", err) - } - - config_obj.Frontend.Certificate = frontend_cert.Cert - config_obj.Frontend.PrivateKey = frontend_cert.PrivateKey - - // Generate gRPC gateway certificate. - gw_certificate, err := crypto.GenerateServerCert( - config_obj, utils.GetGatewayName(config_obj)) - if err != nil { - return fmt.Errorf("Unable to create Frontend cert: %w", err) - } - - config_obj.GUI.GwCertificate = gw_certificate.Cert - config_obj.GUI.GwPrivateKey = gw_certificate.PrivateKey - - return nil -} - func doGenerateConfigNonInteractive() error { logging.DisableLogging() @@ -251,7 +205,7 @@ func doGenerateConfigNonInteractive() error { logging.SuppressLogging = true config_obj := config.GetDefaultConfig() - err := generateNewKeys(config_obj) + err := vsurvey.GenerateNewKeys(config_obj) if err != nil { return fmt.Errorf("Unable to create config: %w", err) } @@ -452,10 +406,7 @@ func doDumpApiClientConfig() error { password := "" if *config_api_client_password_protect { - err = survey.AskOne( - &survey.Password{Message: "Password:"}, - &password, - survey.WithValidator(survey.Required)) + password, err = vsurvey.GetAPIClientPassword() if err != nil { return err } @@ -570,6 +521,31 @@ func doDumpApiClientConfig() error { return nil } +func doGenerateConfigInteractive() error { + config_obj, err := vsurvey.GetInteractiveConfig() + if err != nil { + return err + } + + return vsurvey.StoreServerConfig(config_obj) +} + +func doConfigFrontend() error { + logging.DisableLogging() + + config_obj, err := makeDefaultConfigLoader(). + WithRequiredFrontend().LoadAndValidate() + if err != nil { + return err + } + + if config_obj.Frontend == nil { + return errors.New("Must provide a frontend config") + } + + return vsurvey.GenerateFrontendPackages(config_obj) +} + func init() { command_handlers = append(command_handlers, func(command string) bool { switch command { @@ -595,6 +571,9 @@ func init() { case config_api_client_command.FullCommand(): FatalIfError(config_api_client_command, doDumpApiClientConfig) + case config_frontend_command.FullCommand(): + FatalIfError(config_frontend_command, doConfigFrontend) + default: return false } diff --git a/bin/config_frontend.go b/bin/config_frontend.go deleted file mode 100644 index 21b6b0ded7f..00000000000 --- a/bin/config_frontend.go +++ /dev/null @@ -1,189 +0,0 @@ -// +build !aix - -package main - -import ( - "fmt" - - "github.com/AlecAivazis/survey/v2" - errors "github.com/go-errors/errors" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" - logging "www.velocidex.com/golang/velociraptor/logging" - "www.velocidex.com/golang/velociraptor/services" - "www.velocidex.com/golang/velociraptor/utils" -) - -var ( - config_frontend_command = config_command.Command( - "frontend", "Experimental: Create multi-frontend configuration") -) - -func reportCurrentSetup(config_obj *config_proto.Config) string { - result := fmt.Sprintf("Master frontend is at %v:%v \n", - config_obj.Frontend.Hostname, config_obj.Frontend.BindPort) - - if len(config_obj.ExtraFrontends) > 0 { - result += fmt.Sprintf("Currently configured %v minion frontends\n\n", - len(config_obj.ExtraFrontends)) - for idx, frontend := range config_obj.ExtraFrontends { - result += fmt.Sprintf("Minion %v: %v:%v\n", idx+1, - frontend.Hostname, frontend.BindPort) - } - } - - return result -} - -func doConfigFrontend() error { - logging.DisableLogging() - - config_obj, err := makeDefaultConfigLoader(). - WithRequiredFrontend().LoadAndValidate() - if err != nil { - return err - } - - doit := false - err = survey.AskOne(&survey.Confirm{ - Message: ` -Welcome to the Velociraptor multi-frontend configuration generator ------------------------------------------------------------------- - -Warning: This configuration is currently experiemental. Read more about -it here https://docs.velociraptor.app/docs/deployment/cloud/multifrontend/ - -I will be adding an extra minion frontend to the configuration. -I will not be changing the master frontend configuration at all. - -` + reportCurrentSetup(config_obj) + ` - -Do you wish to continue?`, - }, &doit, survey.WithValidator(survey.Required)) - if err != nil { - return err - } - if !doit { - return err - } - - // Create a new frontend config - frontend_config := &config_proto.FrontendConfig{ - BindAddress: "0.0.0.0", - } - - // Set a better default for the url question - url_question.Default = config_obj.Frontend.Hostname - - // Figure out the install type - if config_obj.Client.UseSelfSignedSsl { - err = survey.Ask([]*survey.Question{ - {Name: "Hostname", Prompt: &survey.Input{ - Message: "What is the public name of the minion frontend (e.g. 192.168.1.22 or ns2.example.com)", - Default: config_obj.Frontend.Hostname, - }}, - {Name: "BindPort", Prompt: &survey.Input{ - Message: "What port should this frontend listen on?", - Default: fmt.Sprintf("%v", config_obj.Frontend.BindPort), - }}, - }, frontend_config, survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - // Add the frontend into the client's configuration. - if frontend_config.BindPort != 443 { - config_obj.Client.ServerUrls = append(config_obj.Client.ServerUrls, - fmt.Sprintf("https://%v:%v/", frontend_config.Hostname, - frontend_config.BindPort)) - } else { - config_obj.Client.ServerUrls = append(config_obj.Client.ServerUrls, - fmt.Sprintf("https://%v/", frontend_config.Hostname)) - } - - } else { - err = survey.AskOne(url_question, &frontend_config.Hostname, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - frontend_config.BindPort = 443 - } - - // Check for validity - if services.GetNodeName(frontend_config) == - services.GetNodeName(config_obj.Frontend) { - return errors.New("Node name is the same as existing master") - } - - for _, fe := range config_obj.ExtraFrontends { - if services.GetNodeName(frontend_config) == - services.GetNodeName(fe) { - return errors.New("Node name is the same as an existing minion") - } - } - - if config_obj.Frontend.DynDns != nil && - config_obj.Frontend.DynDns.DdnsUsername != "" { - err = dynDNSConfig(frontend_config) - if err != nil { - return err - } - } - - // Add the additional frontend. - config_obj.ExtraFrontends = append(config_obj.ExtraFrontends, - frontend_config) - - // API server must be exposed to allow multiple frontends to - // call it. - fmt.Println("I will enable API server to listen on all ports") - config_obj.API.BindAddress = "0.0.0.0" - - // Enable caching datastores - fmt.Println("Master will use MemcacheFileDataStore (Memory cached filestore)") - config_obj.Datastore.MasterImplementation = "MemcacheFileDataStore" - - fmt.Println("Minion will use RemoteFileDataStore (replicating to master).") - config_obj.Datastore.MinionImplementation = "RemoteFileDataStore" - config_obj.Datastore.Implementation = "" - - fmt.Printf("\nNOTE: Both Master and Minion expect an EFS volume mounted on %v within their respective VM/container.\nPlease ensure this is true in deployment!\n\n", - config_obj.Datastore.Location) - - // Adjust clients' config to load balance to all frontends. - for _, fe := range config_obj.ExtraFrontends { - connection_string := fmt.Sprintf("https://%v:%v/", - fe.Hostname, fe.BindPort) - - if !utils.InString(config_obj.Client.ServerUrls, connection_string) { - config_obj.Client.ServerUrls = append(config_obj.Client.ServerUrls, - connection_string) - } - } - - fmt.Printf("Clients will load balance between the following frontends:\n") - for _, url := range config_obj.Client.ServerUrls { - fmt.Printf(" %v\n", url) - } - - err = storeServerConfig(config_obj) - if err != nil { - return err - } - - return storeClientConfig(config_obj) -} - -func init() { - command_handlers = append(command_handlers, func(command string) bool { - switch command { - case config_frontend_command.FullCommand(): - FatalIfError(config_frontend_command, doConfigFrontend) - - default: - return false - } - - return true - }) -} diff --git a/bin/config_interactive.go b/bin/config_interactive.go deleted file mode 100644 index a193746550d..00000000000 --- a/bin/config_interactive.go +++ /dev/null @@ -1,673 +0,0 @@ -//go:build !aix -// +build !aix - -package main - -import ( - "encoding/hex" - "fmt" - "os" - "path" - "reflect" - "regexp" - "runtime" - - "github.com/AlecAivazis/survey/v2" - "github.com/Velocidex/yaml/v2" - "www.velocidex.com/golang/velociraptor/config" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" - logging "www.velocidex.com/golang/velociraptor/logging" - "www.velocidex.com/golang/velociraptor/services/users" - "www.velocidex.com/golang/velociraptor/utils" - "www.velocidex.com/golang/velociraptor/utils/tempfile" -) - -const ( - self_signed = "Self Signed SSL" - autocert = "Automatically provision certificates with Lets Encrypt" - oauth_sso = "Authenticate users with SSO" - - // FileStore implementations - filebased_datastore = "FileBaseDataStore" -) - -var ( - sso_type = &survey.Select{ - Message: "Select the SSO Authentication Provider", - Default: "Google", - Options: []string{"Google", "GitHub", "Azure", "OIDC"}, - } - - server_type_question = &survey.Select{ - Message: ` -Welcome to the Velociraptor configuration generator ---------------------------------------------------- - -I will be creating a new deployment configuration for you. I will -begin by identifying what type of deployment you need. - - -What OS will the server be deployed on? -`, - Default: runtime.GOOS, - Options: []string{"linux", "windows", "darwin"}, - } - - dyndns_provider_quetion = &survey.Select{ - Message: `Which DynDns provider do you use?`, - Default: "none", - Options: []string{"none", "noip", "cloudflare"}, - } - - url_question = &survey.Input{ - Message: "What is the public DNS name of the Master Frontend " + - "(e.g. www.example.com):", - Help: "Clients will connect to the Frontend using this " + - "public name (e.g. https://www.example.com:8000/ ).", - Default: "localhost", - } - - // https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/naming-conventions-for-computer-domain-site-ou#dns-host-names - url_validator = regexValidator("^[a-z0-9.A-Z\\-]+$") - frontend_port_question = &survey.Input{ - Message: "Enter the frontend port to listen on.", - Default: "8000", - } - port_validator = regexValidator("^[0-9]+$") - - should_use_websocket = false - websocket_question = &survey.Confirm{ - Message: `Would you like to try the new experimental websocket comms? - -Websocket is a bidirectional low latency communication protocol supported by -most modern proxies and load balancers. This method is more efficient and -portable than plain HTTP. Be sure to test this in your environment. -`, - } - - gui_port_question = &survey.Input{ - Message: "Enter the port for the GUI to listen on.", - Default: "8889", - } - - log_question = &survey.Input{ - Message: "Path to the logs directory.", - Default: tempfile.GetTempDir(), - } - - expiry_question = &survey.Select{ - Message: `Would you like to extend certificate expiration? - -By defaults internal certificates are issued for 1 year. - -If you expect this deployment to exist part one year you might -consider extending the default validation. -`, - Default: "1 Year", - Options: []string{"1 Year", "5 Years", "10 Years"}, - } - - output_question = &survey.Input{ - Message: "Where should I write the server config file?", - Default: "server.config.yaml", - } - - client_output_question = &survey.Input{ - Message: "Where should I write the client config file?", - Default: "client.config.yaml", - } - - user_name_question = &survey.Input{ - Message: "GUI Username or email address to authorize (empty to end):", - } - - password_question = &survey.Password{ - Message: "Password", - } - - google_oauth = []*survey.Question{ - { - Name: "OauthClientId", - Prompt: &survey.Input{ - Message: "Enter the OAuth Client ID?", - }, - }, { - Name: "OauthClientSecret", - Prompt: &survey.Input{ - Message: "Enter the OAuth Client Secret?", - }, - }, - } - - add_allow_list_question = &survey.Confirm{ - Message: `Do you want to restrict VQL functionality on the server? - -This is useful for a shared server where users are not fully trusted. -It removes potentially dangerous plugins like execve(), filesystem access etc. - -NOTE: This is an experimental feature only useful in limited situations. If you -do not know you need it select N here! -`, - } - - registry_writeback_question = &survey.Confirm{ - Message: `Would you like to use the registry to store the writeback files? (Experimental)`, - } -) - -func regexValidator(re string) survey.Validator { - compiled_re := regexp.MustCompile(re) - - return func(val interface{}) error { - s, ok := val.(string) - if !ok { - return fmt.Errorf("cannot regex on type %v", reflect.TypeOf(val).Name()) - } - - match := compiled_re.MatchString(s) - if !match { - return fmt.Errorf("Invalid format") - } - return nil - } -} - -func configureDataStore(config_obj *config_proto.Config) error { - // For now the file based datastore is the only one supported. - config_obj.Datastore.Implementation = filebased_datastore - - // Configure the data store - var default_data_store string - switch config_obj.ServerType { - case "windows": - default_data_store = "C:\\Windows\\Temp" - default: - default_data_store = "/opt/velociraptor" - } - - data_store_file := []*survey.Question{ - { - Name: "Location", - Prompt: &survey.Input{ - Message: "Path to the datastore directory.", - Default: default_data_store, - }, - }, - } - - err := survey.Ask(data_store_file, - config_obj.Datastore, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - config_obj.Datastore.FilestoreDirectory = config_obj.Datastore.Location - log_question.Default = path.Join(config_obj.Datastore.Location, "logs") - - return nil -} - -func configureDeploymentType(config_obj *config_proto.Config) error { - // What type of install do we need? - install_type := "" - err := survey.AskOne(&survey.Select{ - Options: []string{self_signed, autocert, oauth_sso}, - }, &install_type, nil) - if err != nil { - return err - } - - switch install_type { - case self_signed: - err = configSelfSigned(config_obj) - if err != nil { - return err - } - - case autocert: - err = configAutocert(config_obj) - if err != nil { - return err - } - - case oauth_sso: - err = configAutocert(config_obj) - if err != nil { - return err - } - - config_obj.AutocertCertCache = config_obj.Datastore.Location - config_obj.GUI.Authenticator = &config_proto.Authenticator{} - err = configureSSO(config_obj) - if err != nil { - return err - } - } - - return nil -} - -func configureRegistryWriteback(config_obj *config_proto.Config) error { - use_registry := false - err := survey.AskOne(registry_writeback_question, &use_registry, nil) - if err != nil { - return err - } - - if use_registry { - config_obj.Client.WritebackWindows = "HKLM\\SOFTWARE\\Velocidex\\Velociraptor" - } - return nil -} - -func doGenerateConfigInteractive() error { - logging.DisableLogging() - - config_obj := config.GetDefaultConfig() - - // Figure out which type of server we have. - err := survey.AskOne(server_type_question, - &config_obj.ServerType, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - configureDataStore(config_obj) - configureDeploymentType(config_obj) - configureRegistryWriteback(config_obj) - - // The API's public DNS name allows external callers but by - // default we bind to loopback only. - config_obj.API.Hostname = config_obj.Frontend.Hostname - config_obj.API.BindAddress = "127.0.0.1" - - // Setup dyndns - err = dynDNSConfig(config_obj.Frontend) - if err != nil { - return err - } - - // Add users to the config file so the server can be - // initialized. - err = addUser(config_obj) - if err != nil { - return fmt.Errorf("Add users: %w", err) - } - - expiration := "" - err = survey.AskOne(expiry_question, - &expiration, survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - if config_obj.Defaults == nil { - config_obj.Defaults = &config_proto.Defaults{} - } - - switch expiration { - case "1 Year": - config_obj.Defaults.CertificateValidityDays = 365 - case "5 Years": - config_obj.Defaults.CertificateValidityDays = 365 * 5 - case "10 Years": - config_obj.Defaults.CertificateValidityDays = 365 * 10 - } - - logger := logging.GetLogger(config_obj, &logging.ToolComponent) - logger.Info("Generating keys please wait....") - err = generateNewKeys(config_obj) - if err != nil { - return err - } - - err = survey.AskOne(log_question, - &config_obj.Logging.OutputDirectory, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - config_obj.Logging.SeparateLogsPerComponent = true - - // By default disabled debug logging - it is not useful unless - // you are trying to debug something. - config_obj.Logging.Debug = &config_proto.LoggingRetentionConfig{ - Disabled: true, - } - - err = addAllowList(config_obj) - if err != nil { - return err - } - - storeServerConfig(config_obj) - storeClientConfig(config_obj) - return nil -} - -func storeClientConfig(config_obj *config_proto.Config) error { - path := "" - err := survey.AskOne(client_output_question, &path, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - client_config := getClientConfig(config_obj) - res, err := yaml.Marshal(client_config) - if err != nil { - return fmt.Errorf("Yaml Marshal: %w", err) - } - - fd, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return fmt.Errorf("Open file %s: %w", path, err) - } - _, err = fd.Write(res) - if err != nil { - return fmt.Errorf("Write file %s: %w", path, err) - } - fd.Close() - - return nil -} - -func storeServerConfig(config_obj *config_proto.Config) error { - path := "" - err := survey.AskOne(output_question, &path, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - res, err := yaml.Marshal(config_obj) - if err != nil { - return fmt.Errorf("Yaml Marshal: %w", err) - } - - fd, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return fmt.Errorf("Open file %s: %w", path, err) - } - _, err = fd.Write(res) - if err != nil { - return fmt.Errorf("Write file %s: %w", path, err) - } - fd.Close() - return nil -} - -func configureSSO(config_obj *config_proto.Config) error { - // Which flavor of SSO do we want? - err := survey.AskOne(sso_type, - &config_obj.GUI.Authenticator.Type, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - // Provide the user with a hint about the redirect URL - redirect, err := utils.GetBaseURL(config_obj) - if err != nil { - return err - } - switch config_obj.GUI.Authenticator.Type { - case "Google": - redirect.Path = path.Join(redirect.Path, "auth/google/callback") - case "GitHub": - redirect.Path = path.Join(redirect.Path, "auth/github/callback") - case "Azure": - redirect.Path = path.Join(redirect.Path, "auth/azure/callback") - case "OIDC": - redirect.Path = path.Join(redirect.Path, "auth/oidc/callback") - } - fmt.Printf("\nSetting %v configuration will use redirect URL %v\n", - config_obj.GUI.Authenticator.Type, redirect.String()) - - switch config_obj.GUI.Authenticator.Type { - case "Google", "GitHub": - err = survey.Ask(google_oauth, - config_obj.GUI.Authenticator, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - case "Azure": - // Azure also requires the tenant ID - google_oauth = append(google_oauth, &survey.Question{ - Name: "Tenant", - Prompt: &survey.Input{ - Message: "Enter the Tenant Domain name or ID?", - }, - }) - - err = survey.Ask(google_oauth, - config_obj.GUI.Authenticator, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - case "OIDC": - // OIDC require Issuer URL - google_oauth = append(google_oauth, &survey.Question{ - Name: "OidcIssuer", - Prompt: &survey.Input{ - Message: "Enter valid OIDC Issuer URL", - Help: "e.g. https://accounts.google.com or https://your-org-name.okta.com are valid Issuer URLs, check that URL has /.well-known/openid-configuration endpoint", - }, - Validate: func(val interface{}) error { - // A check to avoid double slashes - if str, ok := val.(string); !ok || str[len(str)-1:] == "/" { - return fmt.Errorf("Issuer URL should not have / (slash) sign as the last symbol") - } - return nil - }, - }) - - err = survey.Ask(google_oauth, - config_obj.GUI.Authenticator, - survey.WithValidator(survey.Required)) - if err != nil { - return err - } - } - return nil -} - -func dynDNSConfig(frontend *config_proto.FrontendConfig) error { - dyndns := "" - err := survey.AskOne(dyndns_provider_quetion, - &dyndns, survey.WithValidator(survey.Required)) - if err != nil { - return err - } - - switch dyndns { - case "none": - return nil - - case "noip": - if frontend.DynDns == nil { - frontend.DynDns = &config_proto.DynDNSConfig{ - Type: "noip", - } - } - - return survey.Ask([]*survey.Question{ - {Name: "DdnsUsername", Prompt: &survey.Input{ - Message: "NoIP DynDNS Username"}}, - {Name: "DdnsPassword", Prompt: &survey.Input{ - Message: "NoIP DynDNS Password"}}, - }, frontend.DynDns, survey.WithValidator(survey.Required)) - - case "cloudflare": - if frontend.DynDns == nil { - frontend.DynDns = &config_proto.DynDNSConfig{ - Type: "cloudflare", - } - } - - return survey.Ask([]*survey.Question{ - {Name: "ZoneName", Prompt: &survey.Input{ - Message: "Cloudflare Zone Name"}}, - {Name: "ApiToken", Prompt: &survey.Input{ - Message: "Cloudflare API Token"}}, - }, frontend.DynDns, survey.WithValidator(survey.Required)) - } - return nil -} - -func configSelfSigned(config_obj *config_proto.Config) error { - err := survey.Ask([]*survey.Question{ - { - Name: "Hostname", - Prompt: url_question, - Validate: url_validator, - }, - { - Name: "BindPort", - Prompt: frontend_port_question, - Validate: port_validator, - }, - }, config_obj.Frontend) - if err != nil { - return err - } - - err = survey.Ask([]*survey.Question{ - { - Name: "BindPort", - Validate: port_validator, - Prompt: gui_port_question, - }, - }, config_obj.GUI) - if err != nil { - return err - } - - err = survey.AskOne(websocket_question, &should_use_websocket, nil) - if err != nil { - return err - } - - protocol := "https" - if should_use_websocket { - protocol = "wss" - } - - config_obj.GUI.PublicUrl = fmt.Sprintf( - "https://%s:%d/", config_obj.Frontend.Hostname, - config_obj.GUI.BindPort) - - config_obj.Client.UseSelfSignedSsl = true - config_obj.Client.ServerUrls = append( - config_obj.Client.ServerUrls, - fmt.Sprintf("%s://%s:%d/", protocol, config_obj.Frontend.Hostname, - config_obj.Frontend.BindPort)) - - config_obj.GUI.Authenticator = &config_proto.Authenticator{ - Type: "Basic"} - - return err -} - -func configAutocert(config_obj *config_proto.Config) error { - err := survey.Ask([]*survey.Question{{ - Name: "Hostname", - Validate: url_validator, - Prompt: url_question, - }, - }, config_obj.Frontend) - if err != nil { - return err - } - - err = survey.AskOne(websocket_question, &should_use_websocket, nil) - if err != nil { - return err - } - - // In autocert mode these are all fixed. - config_obj.Frontend.BindPort = 443 - config_obj.Frontend.BindAddress = "0.0.0.0" - - // The gui is also served from port 443. - config_obj.GUI.BindPort = 443 - config_obj.GUI.PublicUrl = fmt.Sprintf( - "https://%s/", config_obj.Frontend.Hostname) - - config_obj.Client.ServerUrls = []string{ - fmt.Sprintf("https://%s/", config_obj.Frontend.Hostname)} - - config_obj.AutocertCertCache = config_obj.Datastore.Location - - return nil -} - -func addUser(config_obj *config_proto.Config) error { - for { - username := "" - err := survey.AskOne(user_name_question, &username, nil) - if err != nil { - fmt.Printf("%v", err) - continue - } - - if username == "" { - return nil - } - - user_record, err := users.NewUserRecord(config_obj, username) - if err != nil { - fmt.Printf("%v", err) - continue - } - - auth_type := config_obj.GUI.Authenticator.Type - - if auth_type != "Basic" { - fmt.Printf("Authentication will occur via %v - "+ - "therefore no password needs to be set.", - auth_type) - } else { - password := "" - err := survey.AskOne(password_question, &password, - survey.WithValidator(survey.Required)) - if err != nil { - fmt.Printf("%v", err) - continue - } - - users.SetPassword(user_record, password) - } - config_obj.GUI.InitialUsers = append( - config_obj.GUI.InitialUsers, - &config_proto.GUIUser{ - Name: user_record.Name, - PasswordHash: hex.EncodeToString(user_record.PasswordHash), - PasswordSalt: hex.EncodeToString(user_record.PasswordSalt), - }) - } -} - -func addAllowList(config_obj *config_proto.Config) error { - add_allow_list := false - err := survey.AskOne(add_allow_list_question, &add_allow_list, nil) - if err != nil { - return err - } - - if !add_allow_list { - return nil - } - - config_obj.Defaults.AllowedPlugins = allowed_plugins - config_obj.Defaults.AllowedFunctions = allowed_functions - config_obj.Defaults.AllowedAccessors = allowed_accessors - - return nil -} diff --git a/bin/gui.go b/bin/gui.go index f91706925c1..3a69d5d53f0 100644 --- a/bin/gui.go +++ b/bin/gui.go @@ -16,6 +16,7 @@ import ( "www.velocidex.com/golang/velociraptor/services/users" "www.velocidex.com/golang/velociraptor/services/writeback" "www.velocidex.com/golang/velociraptor/startup" + vsurvey "www.velocidex.com/golang/velociraptor/tools/survey" "www.velocidex.com/golang/velociraptor/utils/tempfile" ) @@ -37,7 +38,7 @@ var ( func generateGUIConfig(datastore_directory, server_config_path, client_config_path string) ( *config_proto.Config, error) { config_obj := config.GetDefaultConfig() - err := generateNewKeys(config_obj) + err := vsurvey.GenerateNewKeys(config_obj) if err != nil { return nil, fmt.Errorf("Unable to create config: %w", err) } diff --git a/bin/main.go b/bin/main.go index 88c8440fc7f..eb46e2bb7d5 100755 --- a/bin/main.go +++ b/bin/main.go @@ -26,12 +26,12 @@ import ( "runtime/trace" "time" - "github.com/AlecAivazis/survey/v2" kingpin "github.com/alecthomas/kingpin/v2" errors "github.com/go-errors/errors" "www.velocidex.com/golang/velociraptor/config" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/logging" + vsurvey "www.velocidex.com/golang/velociraptor/tools/survey" "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/velociraptor/utils/proxy" @@ -102,11 +102,7 @@ func maybe_unlock_api_config(config_obj *config_proto.Config) error { } if x509.IsEncryptedPEMBlock(block) { - password := "" - err := survey.AskOne( - &survey.Password{Message: "Password:"}, - &password, - survey.WithValidator(survey.Required)) + password, err := vsurvey.GetAPIClientDecryptPassword() if err != nil { return err } diff --git a/go.mod b/go.mod index eb5caa698de..1ff91bf3ec7 100644 --- a/go.mod +++ b/go.mod @@ -43,14 +43,12 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hillu/go-ntdll v0.0.0-20220801201350-0d23f057ef1f - github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jonboulle/clockwork v0.3.0 // indirect github.com/juju/ratelimit v1.0.1 github.com/lib/pq v1.10.9 github.com/magefile/mage v1.15.0 - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791 github.com/mattn/go-sqlite3 v1.14.22 @@ -99,7 +97,6 @@ require ( ) require ( - github.com/AlecAivazis/survey/v2 v2.3.6 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 github.com/Masterminds/semver/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.2.2 @@ -126,6 +123,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.17.6 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.8 github.com/aws/aws-sdk-go-v2/service/s3 v1.51.3 + github.com/charmbracelet/huh v0.6.0 github.com/clayscode/Go-Splunk-HTTP/splunk/v2 v2.0.1-0.20221027171526-76a36be4fa02 github.com/coreos/go-oidc/v3 v3.11.0 github.com/elastic/go-libaudit/v2 v2.4.0 @@ -171,6 +169,7 @@ require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect @@ -185,17 +184,26 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.3 // indirect github.com/aws/smithy-go v1.20.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beevik/etree v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/catppuccin/go v0.2.0 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/bubbles v0.20.0 // indirect + github.com/charmbracelet/bubbletea v1.2.4 // indirect + github.com/charmbracelet/lipgloss v1.0.0 // indirect + github.com/charmbracelet/x/ansi v0.5.2 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20241209212528-0eec74ecaa6f // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cilium/ebpf v0.16.0 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/gizak/termui/v3 v3.1.0 // indirect @@ -218,21 +226,25 @@ require ( github.com/hillu/go-yara/v4 v4.3.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/karrick/godirwalk v1.17.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/kr/fs v0.1.0 // indirect github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/lestrrat-go/strftime v1.0.5 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/paulmach/orb v0.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7cb397f9c0d..565dabb648d 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkp cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= -github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= -github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= @@ -31,6 +29,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Depado/bfchroma v1.3.0 h1:zz14vpvySU6S0CL6yGPr1vkFevQecIt8dJdCsMS2JpM= github.com/Depado/bfchroma v1.3.0/go.mod h1:c0bFk0tFmT+clD3TIGurjWCfD/QV8/EebfM3JGr+98M= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -40,8 +40,6 @@ github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmy github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= @@ -149,6 +147,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w= github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= @@ -187,6 +187,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.3 h1:TkiFkSVX990ryWIMBCT4kPqZEgTh github.com/aws/aws-sdk-go-v2/service/sts v1.28.3/go.mod h1:xYNauIUqSuvzlPVb3VB5no/n48YGhmlInD3Uh0Co8Zc= github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -198,11 +200,27 @@ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9 github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= +github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= +github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= +github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.5.2 h1:dEa1x2qdOZXD/6439s+wF7xjV+kZLu/iN00GuXXrU9E= +github.com/charmbracelet/x/ansi v0.5.2/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/exp/strings v0.0.0-20241209212528-0eec74ecaa6f h1:gmrUbkrcJCmzRCx9j6ovWUZLkv6zFJdKKDxuvs8Jbls= +github.com/charmbracelet/x/exp/strings v0.0.0-20241209212528-0eec74ecaa6f/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -217,8 +235,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= @@ -253,6 +269,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -391,9 +409,6 @@ github.com/hillu/go-ntdll v0.0.0-20220801201350-0d23f057ef1f h1:es0IoL1/OOoGYUuv github.com/hillu/go-ntdll v0.0.0-20220801201350-0d23f057ef1f/go.mod h1:cHjYsnAnSckPDx8/H01Y+owD1hf2adLA6VRiw4guEbA= github.com/hillu/go-yara/v4 v4.3.2 h1:HGqUN3ORUduWZbb95RQjut4UzavGDbtt/C6SnGB3Amk= github.com/hillu/go-yara/v4 v4.3.2/go.mod h1:AHEs/FXVMQKVVlT6iG9d+q1BRr0gq0WoAWZQaZ0gS7s= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY= -github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -465,6 +480,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lpar/gzipped v1.1.0 h1:FEQnBzF06KTMh8Wnse6wNJvGwe7+vILQIFzuTq6ipGs= github.com/lpar/gzipped v1.1.0/go.mod h1:JBo67wiCld7AmFYfSNA75NmFG65roJiGwrVohF8uYGE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= @@ -482,9 +499,10 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791 h1:PfHMsLQJwoc0ccjK0sam6J0wQo4s8mOuAo2yQGw+T2U= github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -500,9 +518,6 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -511,6 +526,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE= github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA= @@ -522,6 +539,12 @@ github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGp github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= @@ -796,10 +819,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -808,7 +829,6 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/gui/velociraptor/src/components/vfs/file-list.jsx b/gui/velociraptor/src/components/vfs/file-list.jsx index f327a27bcfb..efb7ad012a9 100644 --- a/gui/velociraptor/src/components/vfs/file-list.jsx +++ b/gui/velociraptor/src/components/vfs/file-list.jsx @@ -30,6 +30,10 @@ import { useContextMenu, } from "react-contexify"; +// If the flow in the running state. Flows can be in several states +// that mean they are still running for our purposes. +const is_running_re = new RegExp("running|waiting|in_progress", "i"); +const is_running = x=>is_running_re.test(x); const POLL_TIME = 2000; @@ -268,7 +272,7 @@ class VeloFileList extends Component { return; } let context = response.data.context; - if (context.state === "RUNNING") { + if (is_running(context.state)) { this.setState({lastRecursiveRefreshData: context}); return; } @@ -340,8 +344,8 @@ class VeloFileList extends Component { client_id: this.props.client.client_id, flow_id: this.state.lastRecursiveDownloadFlowId, }, this.source.token).then((response) => { - let context = response.data.context; - if (!context || context.state === "RUNNING") { + let context = response.data && response.data.context; + if (!context || is_running(context.state)) { this.setState({lastRecursiveDownloadData: context}); return; } @@ -376,6 +380,10 @@ class VeloFileList extends Component { // Determine if the recursive SyncDir button should spin. shouldSpinRecursiveSyncDir = ()=>{ + if (_.isEmpty(this.state.lastRecursiveRefreshData)) { + return false; + }; + return _.isEqual(this.state.current_path, this.props.node.path) && _.isEqual(this.state.recursive_sync_dir_version, this.props.version); } @@ -472,7 +480,7 @@ class VeloFileList extends Component {