|
@@ -0,0 +1,393 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "archive/zip"
|
|
|
+ "bytes"
|
|
|
+ "context"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "net/url"
|
|
|
+ "path/filepath"
|
|
|
+ "regexp"
|
|
|
+ "strings"
|
|
|
+
|
|
|
+ "github.com/blackfireio/osinfo"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/database"
|
|
|
+ "github.com/crowdsecurity/crowdsec/pkg/models"
|
|
|
+ "github.com/go-openapi/strfmt"
|
|
|
+ log "github.com/sirupsen/logrus"
|
|
|
+
|
|
|
+ "github.com/spf13/cobra"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ SUPPORT_METRICS_HUMAN_PATH = "metrics/metrics.human"
|
|
|
+ SUPPORT_METRICS_PROMETHEUS_PATH = "metrics/metrics.prometheus"
|
|
|
+ SUPPORT_VERSION_PATH = "version.txt"
|
|
|
+ SUPPORT_OS_INFO_PATH = "osinfo.txt"
|
|
|
+ SUPPORT_PARSERS_PATH = "hub/parsers.txt"
|
|
|
+ SUPPORT_SCENARIOS_PATH = "hub/scenarios.txt"
|
|
|
+ SUPPORT_COLLECTIONS_PATH = "hub/collections.txt"
|
|
|
+ SUPPORT_POSTOVERFLOWS_PATH = "hub/postoverflows.txt"
|
|
|
+ SUPPORT_BOUNCERS_PATH = "lapi/bouncers.txt"
|
|
|
+ SUPPORT_AGENTS_PATH = "lapi/agents.txt"
|
|
|
+ SUPPORT_CROWDSEC_CONFIG_PATH = "config/crowdsec.yaml"
|
|
|
+ SUPPORT_LAPI_STATUS_PATH = "lapi_status.txt"
|
|
|
+ SUPPORT_CAPI_STATUS_PATH = "capi_status.txt"
|
|
|
+ SUPPORT_ACQUISITION_CONFIG_BASE_PATH = "config/acquis/"
|
|
|
+ SUPPORT_CROWDSEC_PROFILE_PATH = "config/profiles.yaml"
|
|
|
+)
|
|
|
+
|
|
|
+func collectMetrics() ([]byte, []byte, error) {
|
|
|
+ log.Info("Collecting prometheus metrics")
|
|
|
+ err := csConfig.LoadPrometheus()
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if csConfig.Cscli.PrometheusUrl == "" {
|
|
|
+ log.Warn("No Prometheus URL configured, metrics will not be collected")
|
|
|
+ return nil, nil, fmt.Errorf("prometheus_uri is not set")
|
|
|
+ }
|
|
|
+
|
|
|
+ humanMetrics, err := FormatPrometheusMetric(csConfig.Cscli.PrometheusUrl+"/metrics", "human")
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("could not fetch promtheus metrics: %s", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ req, err := http.NewRequest(http.MethodGet, csConfig.Cscli.PrometheusUrl+"/metrics", nil)
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("could not create requests to prometheus endpoint: %s", err)
|
|
|
+ }
|
|
|
+ client := &http.Client{}
|
|
|
+ resp, err := client.Do(req)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("could not get metrics from prometheus endpoint: %s", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("could not read metrics from prometheus endpoint: %s", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return humanMetrics, body, nil
|
|
|
+}
|
|
|
+
|
|
|
+func collectVersion() []byte {
|
|
|
+ log.Info("Collecting version")
|
|
|
+ return []byte(cwversion.ShowStr())
|
|
|
+}
|
|
|
+
|
|
|
+func collectOSInfo() ([]byte, error) {
|
|
|
+ log.Info("Collecting OS info")
|
|
|
+ info, err := osinfo.GetOSInfo()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ w := bytes.NewBuffer(nil)
|
|
|
+ w.WriteString(fmt.Sprintf("Architecture: %s\n", info.Architecture))
|
|
|
+ w.WriteString(fmt.Sprintf("Family: %s\n", info.Family))
|
|
|
+ w.WriteString(fmt.Sprintf("ID: %s\n", info.ID))
|
|
|
+ w.WriteString(fmt.Sprintf("Name: %s\n", info.Name))
|
|
|
+ w.WriteString(fmt.Sprintf("Codename: %s\n", info.Codename))
|
|
|
+ w.WriteString(fmt.Sprintf("Version: %s\n", info.Version))
|
|
|
+ w.WriteString(fmt.Sprintf("Build: %s\n", info.Build))
|
|
|
+
|
|
|
+ return w.Bytes(), nil
|
|
|
+}
|
|
|
+
|
|
|
+func initHub() error {
|
|
|
+ if err := csConfig.LoadHub(); err != nil {
|
|
|
+ return fmt.Errorf("cannot load hub: %s", err)
|
|
|
+ }
|
|
|
+ if csConfig.Hub == nil {
|
|
|
+ return fmt.Errorf("hub not configured")
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := cwhub.SetHubBranch(); err != nil {
|
|
|
+ return fmt.Errorf("cannot set hub branch: %s", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
|
|
|
+ return fmt.Errorf("no hub index found: %s", err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func collectHubItems(itemType string) []byte {
|
|
|
+ log.Infof("Collecting %s list", itemType)
|
|
|
+ items := ListItems([]string{itemType}, []string{}, false, true, all)
|
|
|
+ return items
|
|
|
+}
|
|
|
+
|
|
|
+func collectBouncers(dbClient *database.Client) ([]byte, error) {
|
|
|
+ return getBouncers(dbClient)
|
|
|
+}
|
|
|
+
|
|
|
+func collectAgents(dbClient *database.Client) ([]byte, error) {
|
|
|
+ return getAgents(dbClient)
|
|
|
+}
|
|
|
+
|
|
|
+func collectAPIStatus(login string, password string, endpoint string, prefix string) []byte {
|
|
|
+ if csConfig.API.Client == nil || csConfig.API.Client.Credentials == nil {
|
|
|
+ return []byte("No agent credentials found, are we LAPI ?")
|
|
|
+ }
|
|
|
+ pwd := strfmt.Password(password)
|
|
|
+ apiurl, err := url.Parse(endpoint)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("cannot parse API URL: %s", err.Error()))
|
|
|
+ }
|
|
|
+ scenarios, err := cwhub.GetInstalledScenariosAsString()
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("could not collect scenarios: %s", err.Error()))
|
|
|
+ }
|
|
|
+
|
|
|
+ Client, err = apiclient.NewDefaultClient(apiurl,
|
|
|
+ prefix,
|
|
|
+ fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
|
|
+ nil)
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("could not init client: %s", err.Error()))
|
|
|
+ }
|
|
|
+ t := models.WatcherAuthRequest{
|
|
|
+ MachineID: &login,
|
|
|
+ Password: &pwd,
|
|
|
+ Scenarios: scenarios,
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err = Client.Auth.AuthenticateWatcher(context.Background(), t)
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("Could not authenticate to API: %s", err))
|
|
|
+ } else {
|
|
|
+ return []byte("Successfully authenticated to LAPI")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func collectCrowdsecConfig() []byte {
|
|
|
+ log.Info("Collecting crowdsec config")
|
|
|
+ config, err := ioutil.ReadFile(*csConfig.FilePath)
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("could not read config file: %s", err))
|
|
|
+ }
|
|
|
+
|
|
|
+ r := regexp.MustCompile(`(\s+password:|\s+user:|\s+host:)\s+.*`)
|
|
|
+
|
|
|
+ return r.ReplaceAll(config, []byte("$1 ****REDACTED****"))
|
|
|
+}
|
|
|
+
|
|
|
+func collectCrowdsecProfile() []byte {
|
|
|
+ log.Info("Collecting crowdsec profile")
|
|
|
+ config, err := ioutil.ReadFile(csConfig.API.Server.ProfilesPath)
|
|
|
+ if err != nil {
|
|
|
+ return []byte(fmt.Sprintf("could not read profile file: %s", err))
|
|
|
+ }
|
|
|
+ return config
|
|
|
+}
|
|
|
+
|
|
|
+func collectAcquisitionConfig() map[string][]byte {
|
|
|
+ log.Info("Collecting acquisition config")
|
|
|
+ ret := make(map[string][]byte)
|
|
|
+
|
|
|
+ for _, filename := range csConfig.Crowdsec.AcquisitionFiles {
|
|
|
+ fileContent, err := ioutil.ReadFile(filename)
|
|
|
+ if err != nil {
|
|
|
+ ret[filename] = []byte(fmt.Sprintf("could not read file: %s", err))
|
|
|
+ } else {
|
|
|
+ ret[filename] = fileContent
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+func NewSupportCmd() *cobra.Command {
|
|
|
+ var cmdSupport = &cobra.Command{
|
|
|
+ Use: "support [action]",
|
|
|
+ Short: "Provide commands to help during support",
|
|
|
+ Args: cobra.MinimumNArgs(1),
|
|
|
+ DisableAutoGenTag: true,
|
|
|
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
|
|
+ return nil
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ var outFile string
|
|
|
+
|
|
|
+ cmdDump := &cobra.Command{
|
|
|
+ Use: "dump",
|
|
|
+ Short: "Dump all your configuration to a zip file for easier support",
|
|
|
+ Long: `Dump the following informations:
|
|
|
+- Crowdsec version
|
|
|
+- OS version
|
|
|
+- Installed collections list
|
|
|
+- Installed parsers list
|
|
|
+- Installed scenarios list
|
|
|
+- Installed postoverflows list
|
|
|
+- Bouncers list
|
|
|
+- Machines list
|
|
|
+- CAPI status
|
|
|
+- LAPI status
|
|
|
+- Crowdsec config (sensitive information like username and password are redacted)
|
|
|
+- Crowdsec metrics`,
|
|
|
+ Example: `cscli support dump
|
|
|
+cscli support dump -f /tmp/crowdsec-support.zip
|
|
|
+`,
|
|
|
+ Args: cobra.NoArgs,
|
|
|
+ DisableAutoGenTag: true,
|
|
|
+ Run: func(cmd *cobra.Command, args []string) {
|
|
|
+ var err error
|
|
|
+ var skipHub, skipDB, skipCAPI, skipLAPI, skipAgent bool
|
|
|
+ infos := map[string][]byte{
|
|
|
+ SUPPORT_VERSION_PATH: collectVersion(),
|
|
|
+ }
|
|
|
+
|
|
|
+ if outFile == "" {
|
|
|
+ outFile = "/tmp/crowdsec-support.zip"
|
|
|
+ }
|
|
|
+
|
|
|
+ dbClient, err = database.NewClient(csConfig.DbConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ log.Warnf("Could not connect to database: %s", err)
|
|
|
+ skipDB = true
|
|
|
+ infos[SUPPORT_BOUNCERS_PATH] = []byte(err.Error())
|
|
|
+ infos[SUPPORT_AGENTS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := csConfig.LoadAPIServer(); err != nil {
|
|
|
+ log.Warnf("could not load LAPI, skipping CAPI check")
|
|
|
+ skipLAPI = true
|
|
|
+ infos[SUPPORT_CAPI_STATUS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := csConfig.LoadCrowdsec(); err != nil {
|
|
|
+ log.Warnf("could not load agent config, skipping crowdsec config check")
|
|
|
+ skipAgent = true
|
|
|
+ }
|
|
|
+
|
|
|
+ err = initHub()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ log.Warn("Could not init hub, running on LAPI ? Hub related information will not be collected")
|
|
|
+ skipHub = true
|
|
|
+ infos[SUPPORT_PARSERS_PATH] = []byte(err.Error())
|
|
|
+ infos[SUPPORT_SCENARIOS_PATH] = []byte(err.Error())
|
|
|
+ infos[SUPPORT_POSTOVERFLOWS_PATH] = []byte(err.Error())
|
|
|
+ infos[SUPPORT_COLLECTIONS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ if csConfig.API.Client == nil || csConfig.API.Client.Credentials == nil {
|
|
|
+ log.Warn("no agent credentials found, skipping LAPI connectivity check")
|
|
|
+ if _, ok := infos[SUPPORT_LAPI_STATUS_PATH]; ok {
|
|
|
+ infos[SUPPORT_LAPI_STATUS_PATH] = append(infos[SUPPORT_LAPI_STATUS_PATH], []byte("\nNo LAPI credentials found")...)
|
|
|
+ }
|
|
|
+ skipLAPI = true
|
|
|
+ }
|
|
|
+
|
|
|
+ if csConfig.API.Server == nil || csConfig.API.Server.OnlineClient.Credentials == nil {
|
|
|
+ log.Warn("no CAPI credentials found, skipping CAPI connectivity check")
|
|
|
+ skipCAPI = true
|
|
|
+ }
|
|
|
+
|
|
|
+ infos[SUPPORT_METRICS_HUMAN_PATH], infos[SUPPORT_METRICS_PROMETHEUS_PATH], err = collectMetrics()
|
|
|
+ if err != nil {
|
|
|
+ log.Warnf("could not collect prometheus metrics information: %s", err)
|
|
|
+ infos[SUPPORT_METRICS_HUMAN_PATH] = []byte(err.Error())
|
|
|
+ infos[SUPPORT_METRICS_PROMETHEUS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ infos[SUPPORT_OS_INFO_PATH], err = collectOSInfo()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ log.Warnf("could not collect OS information: %s", err)
|
|
|
+ infos[SUPPORT_OS_INFO_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ infos[SUPPORT_CROWDSEC_CONFIG_PATH] = collectCrowdsecConfig()
|
|
|
+
|
|
|
+ if !skipHub {
|
|
|
+ infos[SUPPORT_PARSERS_PATH] = collectHubItems(cwhub.PARSERS)
|
|
|
+ infos[SUPPORT_SCENARIOS_PATH] = collectHubItems(cwhub.SCENARIOS)
|
|
|
+ infos[SUPPORT_POSTOVERFLOWS_PATH] = collectHubItems(cwhub.PARSERS_OVFLW)
|
|
|
+ infos[SUPPORT_COLLECTIONS_PATH] = collectHubItems(cwhub.COLLECTIONS)
|
|
|
+ }
|
|
|
+
|
|
|
+ if !skipDB {
|
|
|
+ infos[SUPPORT_BOUNCERS_PATH], err = collectBouncers(dbClient)
|
|
|
+ if err != nil {
|
|
|
+ log.Warnf("could not collect bouncers information: %s", err)
|
|
|
+ infos[SUPPORT_BOUNCERS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+
|
|
|
+ infos[SUPPORT_AGENTS_PATH], err = collectAgents(dbClient)
|
|
|
+ if err != nil {
|
|
|
+ log.Warnf("could not collect agents information: %s", err)
|
|
|
+ infos[SUPPORT_AGENTS_PATH] = []byte(err.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !skipCAPI {
|
|
|
+ log.Info("Collecting CAPI status")
|
|
|
+ infos[SUPPORT_CAPI_STATUS_PATH] = collectAPIStatus(csConfig.API.Server.OnlineClient.Credentials.Login,
|
|
|
+ csConfig.API.Server.OnlineClient.Credentials.Password,
|
|
|
+ csConfig.API.Server.OnlineClient.Credentials.URL,
|
|
|
+ CAPIURLPrefix)
|
|
|
+ }
|
|
|
+
|
|
|
+ if !skipLAPI {
|
|
|
+ log.Info("Collection LAPI status")
|
|
|
+ infos[SUPPORT_LAPI_STATUS_PATH] = collectAPIStatus(csConfig.API.Client.Credentials.Login,
|
|
|
+ csConfig.API.Client.Credentials.Password,
|
|
|
+ csConfig.API.Client.Credentials.URL,
|
|
|
+ LAPIURLPrefix)
|
|
|
+ infos[SUPPORT_CROWDSEC_PROFILE_PATH] = collectCrowdsecProfile()
|
|
|
+ }
|
|
|
+
|
|
|
+ if !skipAgent {
|
|
|
+
|
|
|
+ acquis := collectAcquisitionConfig()
|
|
|
+
|
|
|
+ for filename, content := range acquis {
|
|
|
+ fname := strings.ReplaceAll(filename, string(filepath.Separator), "___")
|
|
|
+ infos[SUPPORT_ACQUISITION_CONFIG_BASE_PATH+fname] = content
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ w := bytes.NewBuffer(nil)
|
|
|
+ zipWriter := zip.NewWriter(w)
|
|
|
+
|
|
|
+ for filename, data := range infos {
|
|
|
+ fw, err := zipWriter.Create(filename)
|
|
|
+ if err != nil {
|
|
|
+ log.Errorf("Could not add zip entry for %s: %s", filename, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ fw.Write(data)
|
|
|
+ }
|
|
|
+ err = zipWriter.Close()
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("could not finalize zip file: %s", err)
|
|
|
+ }
|
|
|
+ err = ioutil.WriteFile(outFile, w.Bytes(), 0600)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("could not write zip file to %s: %s", outFile, err)
|
|
|
+ }
|
|
|
+ log.Infof("Written zip file to %s", outFile)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ cmdDump.Flags().StringVarP(&outFile, "outFile", "f", "", "File to dump the information to")
|
|
|
+ cmdSupport.AddCommand(cmdDump)
|
|
|
+
|
|
|
+ return cmdSupport
|
|
|
+}
|