diff --git a/cmd/crowdsec-cli/main.go b/cmd/crowdsec-cli/main.go index 7c78abe40..eb4b1a7a1 100644 --- a/cmd/crowdsec-cli/main.go +++ b/cmd/crowdsec-cli/main.go @@ -209,6 +209,21 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall log.Fatalf("failed to hide flag: %s", err) } + // Look for "-c /path/to/config.yaml" + // This duplicates the logic in cobra, but we need to do it before + // because feature flags can change which subcommands are available. + for i, arg := range os.Args { + if arg == "-c" || arg == "--config" { + if len(os.Args) > i+1 { + ConfigFilePath = os.Args[i+1] + } + } + } + + if err := csconfig.LoadFeatureFlagsFile(ConfigFilePath, log.StandardLogger()); err != nil { + log.Fatal(err) + } + if len(os.Args) > 1 { cobra.OnInitialize(initConfig) } @@ -249,7 +264,6 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall } if err := rootCmd.Execute(); err != nil { - log.NewEntry(log.StandardLogger()).Log(log.FatalLevel, err) - os.Exit(1) + log.Fatal(err) } } diff --git a/cmd/crowdsec/main.go b/cmd/crowdsec/main.go index ba8b7d595..1cd92c293 100644 --- a/cmd/crowdsec/main.go +++ b/cmd/crowdsec/main.go @@ -191,9 +191,14 @@ func newLogLevel(curLevelPtr *log.Level, f *Flags) *log.Level { } // LoadConfig returns a configuration parsed from configuration file -func LoadConfig(cConfig *csconfig.Config) error { +func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet bool) (*csconfig.Config, error) { + cConfig, err := csconfig.NewConfig(configFile, disableAgent, disableAPI, quiet) + if err != nil { + return nil, err + } + if (cConfig.Common == nil || *cConfig.Common == csconfig.CommonCfg{}) { - return fmt.Errorf("unable to load configuration: common section is empty") + return nil, fmt.Errorf("unable to load configuration: common section is empty") } cConfig.Common.LogLevel = newLogLevel(cConfig.Common.LogLevel, flags) @@ -207,7 +212,7 @@ func LoadConfig(cConfig *csconfig.Config) error { // Configuration paths are dependency to load crowdsec configuration if err := cConfig.LoadConfigurationPaths(); err != nil { - return err + return nil, err } if flags.SingleFileType != "" && flags.OneShotDSN != "" { @@ -221,31 +226,31 @@ func LoadConfig(cConfig *csconfig.Config) error { cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles, cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs, cConfig.Common.ForceColorLogs); err != nil { - return err + return nil, err } - if err := csconfig.LoadFeatureFlagsFile(cConfig, log.StandardLogger()); err != nil { - return err + if err := csconfig.LoadFeatureFlagsFile(configFile, log.StandardLogger()); err != nil { + return nil, err } if !flags.DisableAgent { if err := cConfig.LoadCrowdsec(); err != nil { - return err + return nil, err } } if !flags.DisableAPI { if err := cConfig.LoadAPIServer(); err != nil { - return err + return nil, err } } if !cConfig.DisableAgent && (cConfig.API == nil || cConfig.API.Client == nil || cConfig.API.Client.Credentials == nil) { - return errors.New("missing local API credentials for crowdsec agent, abort") + return nil, errors.New("missing local API credentials for crowdsec agent, abort") } if cConfig.DisableAPI && cConfig.DisableAgent { - return errors.New("You must run at least the API Server or crowdsec") + return nil, errors.New("You must run at least the API Server or crowdsec") } if flags.TestMode && !cConfig.DisableAgent { @@ -253,15 +258,15 @@ func LoadConfig(cConfig *csconfig.Config) error { } if flags.OneShotDSN != "" && flags.SingleFileType == "" { - return errors.New("-dsn requires a -type argument") + return nil, errors.New("-dsn requires a -type argument") } if flags.Transform != "" && flags.OneShotDSN == "" { - return errors.New("-transform requires a -dsn argument") + return nil, errors.New("-transform requires a -dsn argument") } if flags.SingleFileType != "" && flags.OneShotDSN == "" { - return errors.New("-type requires a -dsn argument") + return nil, errors.New("-type requires a -dsn argument") } if flags.SingleFileType != "" && flags.OneShotDSN != "" { @@ -290,7 +295,7 @@ func LoadConfig(cConfig *csconfig.Config) error { log.Infof("Enabled feature flags: %s", fflist) } - return nil + return cConfig, nil } // crowdsecT0 can be used to measure start time of services, diff --git a/cmd/crowdsec/run_in_svc.go b/cmd/crowdsec/run_in_svc.go index bc68409a3..d655afe88 100644 --- a/cmd/crowdsec/run_in_svc.go +++ b/cmd/crowdsec/run_in_svc.go @@ -34,11 +34,7 @@ func StartRunSvc() error { }, }) - cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) - if err != nil { - return err - } - if err := LoadConfig(cConfig); err != nil { + if cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false); err != nil { return err } diff --git a/cmd/crowdsec/run_in_svc_windows.go b/cmd/crowdsec/run_in_svc_windows.go index 60f2ebe6f..54287f770 100644 --- a/cmd/crowdsec/run_in_svc_windows.go +++ b/cmd/crowdsec/run_in_svc_windows.go @@ -61,13 +61,10 @@ func WindowsRun() error { err error ) - cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) + cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) if err != nil { return err } - if err := LoadConfig(cConfig); err != nil { - return err - } // Configure logging log.Infof("Crowdsec %s", cwversion.VersionStr()) diff --git a/cmd/crowdsec/serve.go b/cmd/crowdsec/serve.go index 4c9ff55a3..6df0f88c1 100644 --- a/cmd/crowdsec/serve.go +++ b/cmd/crowdsec/serve.go @@ -54,15 +54,11 @@ func reloadHandler(sig os.Signal) (*csconfig.Config, error) { crowdsecTomb = tomb.Tomb{} pluginTomb = tomb.Tomb{} - cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) + cConfig, err := LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) if err != nil { return nil, err } - if err = LoadConfig(cConfig); err != nil { - return nil, err - } - if !cConfig.DisableAPI { if flags.DisableCAPI { log.Warningf("Communication with CrowdSec Central API disabled from args") diff --git a/cmd/crowdsec/win_service.go b/cmd/crowdsec/win_service.go index b4cfc3ab0..d0e80c58a 100644 --- a/cmd/crowdsec/win_service.go +++ b/cmd/crowdsec/win_service.go @@ -97,15 +97,11 @@ func runService(name string) error { log.Warnf("Failed to open event log: %s", err) } - cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) + cConfig, err := LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false) if err != nil { return err } - if err := LoadConfig(cConfig); err != nil { - return err - } - log.Infof("starting %s service", name) winsvc := crowdsec_winservice{config: cConfig} diff --git a/pkg/csconfig/cscli.go b/pkg/csconfig/cscli.go index f0dac2b76..6b0bf5ae4 100644 --- a/pkg/csconfig/cscli.go +++ b/pkg/csconfig/cscli.go @@ -1,9 +1,5 @@ package csconfig -import ( - log "github.com/sirupsen/logrus" -) - /*cscli specific config, such as hub directory*/ type CscliCfg struct { Output string `yaml:"output,omitempty"` @@ -26,9 +22,6 @@ func (c *Config) LoadCSCLI() error { if err := c.LoadConfigurationPaths(); err != nil { return err } - if err := LoadFeatureFlagsFile(c, log.StandardLogger()); err != nil { - return err - } c.Cscli.ConfigDir = c.ConfigPaths.ConfigDir c.Cscli.DataDir = c.ConfigPaths.DataDir c.Cscli.HubDir = c.ConfigPaths.HubDir diff --git a/pkg/csconfig/fflag.go b/pkg/csconfig/fflag.go index 3419669bb..e9110649d 100644 --- a/pkg/csconfig/fflag.go +++ b/pkg/csconfig/fflag.go @@ -20,9 +20,12 @@ func LoadFeatureFlagsEnv(logger *log.Logger) error { } -// LoadFeatureFlags parses {ConfigDir}/feature.yaml to enable feature flags. -func LoadFeatureFlagsFile(cConfig *Config, logger *log.Logger) error { - featurePath := filepath.Join(cConfig.ConfigPaths.ConfigDir, "feature.yaml") +// LoadFeatureFlags parses feature.yaml to enable feature flags. +// The file is in the same directory as config.yaml, which is provided +// as the fist parameter. This can be different than ConfigPaths.ConfigDir +func LoadFeatureFlagsFile(configPath string, logger *log.Logger) error { + dir := filepath.Dir(configPath) + featurePath := filepath.Join(dir, "feature.yaml") if err := fflag.Crowdsec.SetFromYamlFile(featurePath, logger); err != nil { return fmt.Errorf("file %s: %s", featurePath, err) diff --git a/test/bats/01_cscli.bats b/test/bats/01_cscli.bats index a8d1b689f..e96a9bc69 100644 --- a/test/bats/01_cscli.bats +++ b/test/bats/01_cscli.bats @@ -282,7 +282,7 @@ teardown() { assert_output - <"$BATS_TEST_DIRNAME"/testdata/explain/explain-log.txt } -@test "Allow variable expansion and literal \$ characters in passwords' { +@test 'Allow variable expansion and literal $ characters in passwords' { export DB_PASSWORD='P@ssw0rd' # shellcheck disable=SC2016 config_set '.db_config.password="$DB_PASSWORD"' @@ -321,3 +321,14 @@ teardown() { rune -0 cscli doc assert_file_exist "doc/cscli_setup.md" } + +@test "feature.yaml for subcommands" { + # it is possible to enable subcommands with feature flags defined in feature.yaml + + rune -1 cscli setup + assert_stderr --partial 'unknown command \"setup\" for \"cscli\"' + CONFIG_DIR=$(dirname "$CONFIG_YAML") + echo ' - cscli_setup' >> "$CONFIG_DIR"/feature.yaml + rune -0 cscli setup + assert_output --partial 'cscli setup [command]' +}