github.com/crowdsecurity/crowdsec@v1.6.1/cmd/crowdsec-cli/main.go (about)

     1  package main
     2  
     3  import (
     4  	"os"
     5  	"slices"
     6  	"time"
     7  
     8  	"github.com/fatih/color"
     9  	cc "github.com/ivanpirog/coloredcobra"
    10  	log "github.com/sirupsen/logrus"
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    14  	"github.com/crowdsecurity/crowdsec/pkg/database"
    15  	"github.com/crowdsecurity/crowdsec/pkg/fflag"
    16  )
    17  
    18  var ConfigFilePath string
    19  var csConfig *csconfig.Config
    20  var dbClient *database.Client
    21  
    22  type configGetter func() *csconfig.Config
    23  
    24  var mergedConfig string
    25  
    26  type cliRoot struct {
    27  	logTrace     bool
    28  	logDebug     bool
    29  	logInfo      bool
    30  	logWarn      bool
    31  	logErr       bool
    32  	outputColor  string
    33  	outputFormat string
    34  	// flagBranch overrides the value in csConfig.Cscli.HubBranch
    35  	flagBranch string
    36  }
    37  
    38  func newCliRoot() *cliRoot {
    39  	return &cliRoot{}
    40  }
    41  
    42  // cfg() is a helper function to get the configuration loaded from config.yaml,
    43  // we pass it to subcommands because the file is not read until the Execute() call
    44  func (cli *cliRoot) cfg() *csconfig.Config {
    45  	return csConfig
    46  }
    47  
    48  // wantedLogLevel returns the log level requested in the command line flags.
    49  func (cli *cliRoot) wantedLogLevel() log.Level {
    50  	switch {
    51  	case cli.logTrace:
    52  		return log.TraceLevel
    53  	case cli.logDebug:
    54  		return log.DebugLevel
    55  	case cli.logInfo:
    56  		return log.InfoLevel
    57  	case cli.logWarn:
    58  		return log.WarnLevel
    59  	case cli.logErr:
    60  		return log.ErrorLevel
    61  	default:
    62  		return log.InfoLevel
    63  	}
    64  }
    65  
    66  // loadConfigFor loads the configuration file for the given sub-command.
    67  // If the sub-command does not need it, it returns a default configuration.
    68  func loadConfigFor(command string) (*csconfig.Config, string, error) {
    69  	noNeedConfig := []string{
    70  		"doc",
    71  		"help",
    72  		"completion",
    73  		"version",
    74  		"hubtest",
    75  	}
    76  
    77  	if !slices.Contains(noNeedConfig, command) {
    78  		log.Debugf("Using %s as configuration file", ConfigFilePath)
    79  
    80  		config, merged, err := csconfig.NewConfig(ConfigFilePath, false, false, true)
    81  		if err != nil {
    82  			return nil, "", err
    83  		}
    84  
    85  		return config, merged, nil
    86  	}
    87  
    88  	return csconfig.NewDefaultConfig(), "", nil
    89  }
    90  
    91  // initialize is called before the subcommand is executed.
    92  func (cli *cliRoot) initialize() {
    93  	var err error
    94  
    95  	log.SetLevel(cli.wantedLogLevel())
    96  
    97  	csConfig, mergedConfig, err = loadConfigFor(os.Args[1])
    98  	if err != nil {
    99  		log.Fatal(err)
   100  	}
   101  
   102  	// recap of the enabled feature flags, because logging
   103  	// was not enabled when we set them from envvars
   104  	if fflist := csconfig.ListFeatureFlags(); fflist != "" {
   105  		log.Debugf("Enabled feature flags: %s", fflist)
   106  	}
   107  
   108  	if cli.flagBranch != "" {
   109  		csConfig.Cscli.HubBranch = cli.flagBranch
   110  	}
   111  
   112  	if cli.outputFormat != "" {
   113  		csConfig.Cscli.Output = cli.outputFormat
   114  	}
   115  
   116  	if csConfig.Cscli.Output == "" {
   117  		csConfig.Cscli.Output = "human"
   118  	}
   119  
   120  	if csConfig.Cscli.Output != "human" && csConfig.Cscli.Output != "json" && csConfig.Cscli.Output != "raw" {
   121  		log.Fatalf("output format '%s' not supported: must be one of human, json, raw", csConfig.Cscli.Output)
   122  	}
   123  
   124  	if csConfig.Cscli.Output == "json" {
   125  		log.SetFormatter(&log.JSONFormatter{})
   126  		log.SetLevel(log.ErrorLevel)
   127  	} else if csConfig.Cscli.Output == "raw" {
   128  		log.SetLevel(log.ErrorLevel)
   129  	}
   130  
   131  	if cli.outputColor != "" {
   132  		csConfig.Cscli.Color = cli.outputColor
   133  
   134  		if cli.outputColor != "yes" && cli.outputColor != "no" && cli.outputColor != "auto" {
   135  			log.Fatalf("output color %s unknown", cli.outputColor)
   136  		}
   137  	}
   138  }
   139  
   140  // list of valid subcommands for the shell completion
   141  var validArgs = []string{
   142  	"alerts", "appsec-configs", "appsec-rules", "bouncers", "capi", "collections",
   143  	"completion", "config", "console", "contexts", "dashboard", "decisions", "explain",
   144  	"hub", "hubtest", "lapi", "machines", "metrics", "notifications", "parsers",
   145  	"postoverflows", "scenarios", "simulation", "support", "version",
   146  }
   147  
   148  func (cli *cliRoot) colorize(cmd *cobra.Command) {
   149  	cc.Init(&cc.Config{
   150  		RootCmd:         cmd,
   151  		Headings:        cc.Yellow,
   152  		Commands:        cc.Green + cc.Bold,
   153  		CmdShortDescr:   cc.Cyan,
   154  		Example:         cc.Italic,
   155  		ExecName:        cc.Bold,
   156  		Aliases:         cc.Bold + cc.Italic,
   157  		FlagsDataType:   cc.White,
   158  		Flags:           cc.Green,
   159  		FlagsDescr:      cc.Cyan,
   160  		NoExtraNewlines: true,
   161  		NoBottomNewline: true,
   162  	})
   163  	cmd.SetOut(color.Output)
   164  }
   165  
   166  func (cli *cliRoot) NewCommand() *cobra.Command {
   167  	// set the formatter asap and worry about level later
   168  	logFormatter := &log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true}
   169  	log.SetFormatter(logFormatter)
   170  
   171  	if err := fflag.RegisterAllFeatures(); err != nil {
   172  		log.Fatalf("failed to register features: %s", err)
   173  	}
   174  
   175  	if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil {
   176  		log.Fatalf("failed to set feature flags from env: %s", err)
   177  	}
   178  
   179  	cmd := &cobra.Command{
   180  		Use:   "cscli",
   181  		Short: "cscli allows you to manage crowdsec",
   182  		Long: `cscli is the main command to interact with your crowdsec service, scenarios & db.
   183  It is meant to allow you to manage bans, parsers/scenarios/etc, api and generally manage you crowdsec setup.`,
   184  		ValidArgs:         validArgs,
   185  		DisableAutoGenTag: true,
   186  		SilenceErrors:     true,
   187  		SilenceUsage:      true,
   188  		/*TBD examples*/
   189  	}
   190  
   191  	cli.colorize(cmd)
   192  
   193  	/*don't sort flags so we can enforce order*/
   194  	cmd.Flags().SortFlags = false
   195  
   196  	pflags := cmd.PersistentFlags()
   197  	pflags.SortFlags = false
   198  
   199  	pflags.StringVarP(&ConfigFilePath, "config", "c", csconfig.DefaultConfigPath("config.yaml"), "path to crowdsec config file")
   200  	pflags.StringVarP(&cli.outputFormat, "output", "o", "", "Output format: human, json, raw")
   201  	pflags.StringVarP(&cli.outputColor, "color", "", "auto", "Output color: yes, no, auto")
   202  	pflags.BoolVar(&cli.logDebug, "debug", false, "Set logging to debug")
   203  	pflags.BoolVar(&cli.logInfo, "info", false, "Set logging to info")
   204  	pflags.BoolVar(&cli.logWarn, "warning", false, "Set logging to warning")
   205  	pflags.BoolVar(&cli.logErr, "error", false, "Set logging to error")
   206  	pflags.BoolVar(&cli.logTrace, "trace", false, "Set logging to trace")
   207  	pflags.StringVar(&cli.flagBranch, "branch", "", "Override hub branch on github")
   208  
   209  	if err := pflags.MarkHidden("branch"); err != nil {
   210  		log.Fatalf("failed to hide flag: %s", err)
   211  	}
   212  
   213  	// Look for "-c /path/to/config.yaml"
   214  	// This duplicates the logic in cobra, but we need to do it before
   215  	// because feature flags can change which subcommands are available.
   216  	for i, arg := range os.Args {
   217  		if arg == "-c" || arg == "--config" {
   218  			if len(os.Args) > i+1 {
   219  				ConfigFilePath = os.Args[i+1]
   220  			}
   221  		}
   222  	}
   223  
   224  	if err := csconfig.LoadFeatureFlagsFile(ConfigFilePath, log.StandardLogger()); err != nil {
   225  		log.Fatal(err)
   226  	}
   227  
   228  	if len(os.Args) > 1 {
   229  		cobra.OnInitialize(cli.initialize)
   230  	}
   231  
   232  	cmd.AddCommand(NewCLIDoc().NewCommand(cmd))
   233  	cmd.AddCommand(NewCLIVersion().NewCommand())
   234  	cmd.AddCommand(NewCLIConfig(cli.cfg).NewCommand())
   235  	cmd.AddCommand(NewCLIHub(cli.cfg).NewCommand())
   236  	cmd.AddCommand(NewCLIMetrics(cli.cfg).NewCommand())
   237  	cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand())
   238  	cmd.AddCommand(NewCLIDecisions(cli.cfg).NewCommand())
   239  	cmd.AddCommand(NewCLIAlerts(cli.cfg).NewCommand())
   240  	cmd.AddCommand(NewCLISimulation(cli.cfg).NewCommand())
   241  	cmd.AddCommand(NewCLIBouncers(cli.cfg).NewCommand())
   242  	cmd.AddCommand(NewCLIMachines(cli.cfg).NewCommand())
   243  	cmd.AddCommand(NewCLICapi(cli.cfg).NewCommand())
   244  	cmd.AddCommand(NewCLILapi(cli.cfg).NewCommand())
   245  	cmd.AddCommand(NewCompletionCmd())
   246  	cmd.AddCommand(NewCLIConsole(cli.cfg).NewCommand())
   247  	cmd.AddCommand(NewCLIExplain(cli.cfg).NewCommand())
   248  	cmd.AddCommand(NewCLIHubTest(cli.cfg).NewCommand())
   249  	cmd.AddCommand(NewCLINotifications(cli.cfg).NewCommand())
   250  	cmd.AddCommand(NewCLISupport().NewCommand())
   251  	cmd.AddCommand(NewCLIPapi(cli.cfg).NewCommand())
   252  	cmd.AddCommand(NewCLICollection().NewCommand())
   253  	cmd.AddCommand(NewCLIParser().NewCommand())
   254  	cmd.AddCommand(NewCLIScenario().NewCommand())
   255  	cmd.AddCommand(NewCLIPostOverflow().NewCommand())
   256  	cmd.AddCommand(NewCLIContext().NewCommand())
   257  	cmd.AddCommand(NewCLIAppsecConfig().NewCommand())
   258  	cmd.AddCommand(NewCLIAppsecRule().NewCommand())
   259  
   260  	if fflag.CscliSetup.IsEnabled() {
   261  		cmd.AddCommand(NewSetupCmd())
   262  	}
   263  
   264  	return cmd
   265  }
   266  
   267  func main() {
   268  	cmd := newCliRoot().NewCommand()
   269  	if err := cmd.Execute(); err != nil {
   270  		log.Fatal(err)
   271  	}
   272  }