From 799cc82bb566bd92bb4a7ab10919e5609bbf231f Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Mon, 6 Jun 2022 15:24:48 +0200 Subject: [PATCH] functional tests, minor refactoring and lint/cleanup (#1570) * cmd/crowdsec: removed log.Fatal()s, added tests and print error for unrecognized argument * updated golangci-lint to v1.46 * lint/deadcode: fix existing issues * tests: cscli config backup/restore * tests: cscli completion powershell/fish * err check: pflags MarkHidden() * empty .dockerignore (and explain the reason) * tests, errors.Wrap * test for CS_LAPI_SECRET and minor refactoring * minor style changes * log cleanup --- .dockerignore | 3 + .../workflows/ci_golangci-lint-windows.yml | 6 +- .github/workflows/ci_golangci-lint.yml | 3 +- .golangci.yml | 26 ++++----- cmd/crowdsec-cli/capi.go | 8 ++- cmd/crowdsec-cli/collections.go | 11 ++-- cmd/crowdsec-cli/config.go | 12 ++-- cmd/crowdsec-cli/configfile.go | 5 -- cmd/crowdsec-cli/configfile_windows.go | 6 -- cmd/crowdsec-cli/console.go | 4 +- cmd/crowdsec-cli/hub.go | 10 ++-- cmd/crowdsec-cli/lapi.go | 4 +- cmd/crowdsec-cli/main.go | 2 +- cmd/crowdsec-cli/messages.go | 19 +++---- cmd/crowdsec-cli/parsers.go | 4 +- cmd/crowdsec-cli/postoverflows.go | 4 +- cmd/crowdsec-cli/scenarios.go | 4 +- cmd/crowdsec-cli/simulation.go | 16 +++--- cmd/crowdsec-cli/utils.go | 2 +- cmd/crowdsec/api.go | 9 ++- cmd/crowdsec/configfile.go | 5 -- cmd/crowdsec/configfile_windows.go | 3 - cmd/crowdsec/main.go | 46 +++++++++++++-- cmd/crowdsec/run_in_svc.go | 22 ++----- cmd/crowdsec/run_in_svc_windows.go | 49 ++++++---------- cmd/crowdsec/serve.go | 51 +++++++++-------- cmd/crowdsec/win_service.go | 33 ++++++----- pkg/acquisition/acquisition_test.go | 1 + pkg/acquisition/modules/file/file.go | 45 ++++++++------- pkg/apiclient/alerts_service_test.go | 8 +-- pkg/apiclient/auth_service_test.go | 8 +-- pkg/apiclient/auth_test.go | 2 +- pkg/apiclient/decisions_service_test.go | 10 ++-- pkg/apiserver/api_key_test.go | 2 +- pkg/apiserver/apiserver.go | 4 +- pkg/apiserver/machines_test.go | 8 +-- pkg/apiserver/middlewares/v1/jwt.go | 45 +++++++++------ pkg/apiserver/testutils.go | 9 --- pkg/apiserver/testutils_windows.go | 7 --- pkg/csconfig/crowdsec_service.go | 2 +- pkg/csconfig/hub_test.go | 8 +-- pkg/cwhub/cwhub_test.go | 1 - pkg/parser/parsing_test.go | 2 +- pkg/parser/runtime.go | 2 +- tests/bats/01_base.bats | 41 +++++++++++-- tests/bats/01_crowdsec.bats | 57 +++++++++++++++++++ tests/bats/06_crowdsec.bats | 5 ++ tests/bats/72_plugin_badconfig.bats | 32 +++++++++++ 48 files changed, 388 insertions(+), 278 deletions(-) create mode 100644 .dockerignore delete mode 100644 cmd/crowdsec-cli/configfile.go delete mode 100644 cmd/crowdsec-cli/configfile_windows.go delete mode 100644 cmd/crowdsec/configfile.go delete mode 100644 cmd/crowdsec/configfile_windows.go delete mode 100644 pkg/apiserver/testutils.go delete mode 100644 pkg/apiserver/testutils_windows.go create mode 100644 tests/bats/01_crowdsec.bats diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..1deda365c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +# We include .git in the build context because excluding it would break the +# "make release" target, which uses git to retrieve the build version and tag. +#.git diff --git a/.github/workflows/ci_golangci-lint-windows.yml b/.github/workflows/ci_golangci-lint-windows.yml index 759175ea6..d777fd0ef 100644 --- a/.github/workflows/ci_golangci-lint-windows.yml +++ b/.github/workflows/ci_golangci-lint-windows.yml @@ -19,12 +19,12 @@ jobs: name: lint-windows runs-on: windows-2022 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.45.2 + version: v1.46 # Optional: golangci-lint command line arguments. args: --issues-exit-code=0 --timeout 10m only-new-issues: true diff --git a/.github/workflows/ci_golangci-lint.yml b/.github/workflows/ci_golangci-lint.yml index 4ea56711d..22271e9a7 100644 --- a/.github/workflows/ci_golangci-lint.yml +++ b/.github/workflows/ci_golangci-lint.yml @@ -19,13 +19,12 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.45 + version: v1.46 # Optional: golangci-lint command line arguments. args: --issues-exit-code=1 --timeout 5m # Optional: show only new issues if it's a pull request. The default value is `false`. diff --git a/.golangci.yml b/.golangci.yml index 2f6372487..79b774a86 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,10 +38,11 @@ linters: # # DEPRECATED by golangi-lint # - - golint # [deprecated]: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes - - interfacer # [deprecated]: Linter that suggests narrower interface types - - maligned # [deprecated]: Tool to detect Go structs that would take less memory if their fields were sorted - - scopelint # [deprecated]: Scopelint checks for unpinned variables in go programs + - exhaustivestruct # The owner seems to have abandoned the linter. Replaced by exhaustruct. + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - interfacer # Linter that suggests narrower interface types + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - scopelint # Scopelint checks for unpinned variables in go programs # # Enabled @@ -96,6 +97,8 @@ linters: - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length - nilerr # Finds the code that returns nil even if it checks that the error is not nil. + - nonamedreturns # Reports all named returns + - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. - predeclared # find code that shadows one of Go's predeclared identifiers - promlinter # Check Prometheus metrics naming via promlint - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. @@ -148,13 +151,14 @@ linters: - testpackage # linter that makes you use a separate _test package # - # Too strict (for now?) + # Too strict / too many false positives (for now?) # + - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds - forbidigo # Forbids identifiers - tagliatelle # Checks the struct tags. - varnamelen # checks that the length of a variable's name matches its scope - gochecknoglobals # check that no global variables exist - - exhaustivestruct # Checks if all struct's fields are initialized + - exhaustruct # Checks if all structure fields are initialized - goconst # Finds repeated strings that could be replaced by a constant - stylecheck # Stylecheck is a replacement for golint @@ -228,16 +232,6 @@ issues: - gosimple text: "S1028: should use .* instead of .*" - # - # deadcode - # - - - linters: - - deadcode - - unused - - structcheck - text: ".* is unused" - # # ineffassign # diff --git a/cmd/crowdsec-cli/capi.go b/cmd/crowdsec-cli/capi.go index c82c17f15..54a4fbebe 100644 --- a/cmd/crowdsec-cli/capi.go +++ b/cmd/crowdsec-cli/capi.go @@ -103,7 +103,9 @@ func NewCapiCmd() *cobra.Command { } cmdCapiRegister.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination") cmdCapiRegister.Flags().StringVar(&capiUserPrefix, "schmilblick", "", "set a schmilblick (use in tests only)") - cmdCapiRegister.Flags().MarkHidden("schmilblick") + if err := cmdCapiRegister.Flags().MarkHidden("schmilblick"); err != nil { + log.Fatalf("failed to hide flag: %s", err) + } cmdCapi.AddCommand(cmdCapiRegister) var cmdCapiStatus = &cobra.Command{ @@ -131,11 +133,11 @@ func NewCapiCmd() *cobra.Command { } if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { - log.Infoln("Run 'sudo cscli hub update' to get the hub index") + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to load hub index : %s", err) } scenarios, err := cwhub.GetInstalledScenariosAsString() diff --git a/cmd/crowdsec-cli/collections.go b/cmd/crowdsec-cli/collections.go index c25cc5105..217d50c60 100644 --- a/cmd/crowdsec-cli/collections.go +++ b/cmd/crowdsec-cli/collections.go @@ -21,7 +21,7 @@ func NewCollectionsCmd() *cobra.Command { DisableAutoGenTag: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if csConfig.Hub == nil { return fmt.Errorf("you must configure cli before interacting with hub") @@ -32,8 +32,8 @@ func NewCollectionsCmd() *cobra.Command { } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } return nil @@ -60,11 +60,10 @@ func NewCollectionsCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { for _, name := range args { if err := cwhub.InstallItem(csConfig, name, cwhub.COLLECTIONS, forceAction, downloadOnly); err != nil { - if ignoreError { - log.Errorf("Error while installing '%s': %s", name, err) - } else { + if !ignoreError { log.Fatalf("Error while installing '%s': %s", name, err) } + log.Errorf("Error while installing '%s': %s", name, err) } } }, @@ -91,7 +90,7 @@ func NewCollectionsCmd() *cobra.Command { } if len(args) == 0 { - log.Fatalf("Specify at least one collection to remove or '--all' flag.") + log.Fatal("Specify at least one collection to remove or '--all' flag.") } for _, name := range args { diff --git a/cmd/crowdsec-cli/config.go b/cmd/crowdsec-cli/config.go index b3898e66f..495f9292f 100644 --- a/cmd/crowdsec-cli/config.go +++ b/cmd/crowdsec-cli/config.go @@ -47,13 +47,13 @@ func backupConfigToDirectory(dirPath string) error { } if err = os.Mkdir(dirPath, 0700); err != nil { - return fmt.Errorf("error while creating %s : %s", dirPath, err) + return errors.Wrapf(err, "while creating %s", dirPath) } if csConfig.ConfigPaths.SimulationFilePath != "" { backupSimulation := filepath.Join(dirPath, "simulation.yaml") if err = types.CopyFile(csConfig.ConfigPaths.SimulationFilePath, backupSimulation); err != nil { - return fmt.Errorf("failed copy %s to %s : %s", csConfig.ConfigPaths.SimulationFilePath, backupSimulation, err) + return errors.Wrapf(err, "failed copy %s to %s", csConfig.ConfigPaths.SimulationFilePath, backupSimulation) } log.Infof("Saved simulation to %s", backupSimulation) } @@ -437,11 +437,11 @@ func NewConfigCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { var err error if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err = cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } if err = backupConfigToDirectory(args[0]); err != nil { log.Fatalf("Failed to backup configurations: %s", err) @@ -466,11 +466,11 @@ func NewConfigCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { var err error if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err = cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } if err := restoreConfigFromDirectory(args[0]); err != nil { log.Fatalf("failed restoring configurations from %s : %s", args[0], err) diff --git a/cmd/crowdsec-cli/configfile.go b/cmd/crowdsec-cli/configfile.go deleted file mode 100644 index c61c41611..000000000 --- a/cmd/crowdsec-cli/configfile.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build linux freebsd netbsd openbsd solaris !windows - -package main - -const DefaultConfigFile = "/etc/crowdsec/config.yaml" diff --git a/cmd/crowdsec-cli/configfile_windows.go b/cmd/crowdsec-cli/configfile_windows.go deleted file mode 100644 index dcb4d92b3..000000000 --- a/cmd/crowdsec-cli/configfile_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build windows -// +build windows - -package main - -const DefaultConfigFile = "C:\\ProgramData\\CrowdSec\\config\\config.yaml" diff --git a/cmd/crowdsec-cli/console.go b/cmd/crowdsec-cli/console.go index d7d05f53b..df26bcf05 100644 --- a/cmd/crowdsec-cli/console.go +++ b/cmd/crowdsec-cli/console.go @@ -78,12 +78,12 @@ After running this command your will need to validate the enrollment in the weba } if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { log.Fatalf("Failed to load hub index : %s", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") + log.Info("Run 'sudo cscli hub update' to get the hub index") } scenarios, err := cwhub.GetInstalledScenariosAsString() diff --git a/cmd/crowdsec-cli/hub.go b/cmd/crowdsec-cli/hub.go index bf1ace9a0..32edda4ce 100644 --- a/cmd/crowdsec-cli/hub.go +++ b/cmd/crowdsec-cli/hub.go @@ -44,11 +44,11 @@ cscli hub update # Download list of available configurations from the hub Run: func(cmd *cobra.Command, args []string) { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } //use LocalSync to get warnings about tainted / outdated items _, warn := cwhub.LocalSync(csConfig.Hub) @@ -84,7 +84,7 @@ Fetches the [.index.json](https://github.com/crowdsecurity/hub/blob/master/.inde }, Run: func(cmd *cobra.Command, args []string) { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.UpdateHubIdx(csConfig.Hub); err != nil { log.Fatalf("Failed to get Hub index : %v", err) @@ -118,11 +118,11 @@ Upgrade all configs installed from Crowdsec Hub. Run 'sudo cscli hub update' if }, Run: func(cmd *cobra.Command, args []string) { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } log.Infof("Upgrading collections") diff --git a/cmd/crowdsec-cli/lapi.go b/cmd/crowdsec-cli/lapi.go index b02167fbc..50b97aa42 100644 --- a/cmd/crowdsec-cli/lapi.go +++ b/cmd/crowdsec-cli/lapi.go @@ -134,12 +134,12 @@ Keep in mind the machine needs to be validated by an administrator on LAPI side log.Fatalf("parsing api url ('%s'): %s", apiurl, err) } if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to load hub index : %s", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } scenarios, err := cwhub.GetInstalledScenariosAsString() if err != nil { diff --git a/cmd/crowdsec-cli/main.go b/cmd/crowdsec-cli/main.go index 5f480a8d0..81ec2013b 100644 --- a/cmd/crowdsec-cli/main.go +++ b/cmd/crowdsec-cli/main.go @@ -155,7 +155,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall rootCmd.PersistentFlags().StringVar(&cwhub.HubBranch, "branch", "", "Override hub branch on github") if err := rootCmd.PersistentFlags().MarkHidden("branch"); err != nil { - log.Fatalf("failed to make branch hidden : %s", err) + log.Fatalf("failed to hide flag: %s", err) } if len(os.Args) > 1 && os.Args[1] != "completion" && os.Args[1] != "version" && os.Args[1] != "help" { diff --git a/cmd/crowdsec-cli/messages.go b/cmd/crowdsec-cli/messages.go index 2e6740258..02f051601 100644 --- a/cmd/crowdsec-cli/messages.go +++ b/cmd/crowdsec-cli/messages.go @@ -5,24 +5,19 @@ import ( "runtime" ) -const ( - ReloadMessageFormat = `Run '%s' for the new configuration to be effective.` - ReloadCmdLinux = `sudo systemctl reload crowdsec` - ReloadCmdFreebsd = `sudo service crowdsec reload` -) - +// ReloadMessage returns a description of the task required to reload +// the crowdsec configuration, according to the operating system. func ReloadMessage() string { - - var reloadCmd string + var msg string switch runtime.GOOS { case "windows": - return "Please restart the crowdsec service for the new configuration to be effective." + msg = "Please restart the crowdsec service" case "freebsd": - reloadCmd = ReloadCmdFreebsd + msg = `Run 'sudo service crowdsec reload'` default: - reloadCmd = ReloadCmdLinux + msg = `Run 'sudo systemctl reload crowdsec'` } - return fmt.Sprintf(ReloadMessageFormat, reloadCmd) + return fmt.Sprintf("%s for the new configuration to be effective.", msg) } diff --git a/cmd/crowdsec-cli/parsers.go b/cmd/crowdsec-cli/parsers.go index 0854ba309..4905fe140 100644 --- a/cmd/crowdsec-cli/parsers.go +++ b/cmd/crowdsec-cli/parsers.go @@ -25,7 +25,7 @@ cscli parsers remove crowdsecurity/sshd-logs DisableAutoGenTag: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if csConfig.Hub == nil { return fmt.Errorf("you must configure cli before interacting with hub") @@ -36,8 +36,8 @@ cscli parsers remove crowdsecurity/sshd-logs } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } return nil }, diff --git a/cmd/crowdsec-cli/postoverflows.go b/cmd/crowdsec-cli/postoverflows.go index e09af9b28..e8b80e11c 100644 --- a/cmd/crowdsec-cli/postoverflows.go +++ b/cmd/crowdsec-cli/postoverflows.go @@ -24,7 +24,7 @@ func NewPostOverflowsCmd() *cobra.Command { DisableAutoGenTag: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if csConfig.Hub == nil { return fmt.Errorf("you must configure cli before interacting with hub") @@ -35,8 +35,8 @@ func NewPostOverflowsCmd() *cobra.Command { } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } return nil }, diff --git a/cmd/crowdsec-cli/scenarios.go b/cmd/crowdsec-cli/scenarios.go index c1d2896ae..8818dcb5c 100644 --- a/cmd/crowdsec-cli/scenarios.go +++ b/cmd/crowdsec-cli/scenarios.go @@ -27,7 +27,7 @@ cscli scenarios remove crowdsecurity/ssh-bf DisableAutoGenTag: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if csConfig.Hub == nil { return fmt.Errorf("you must configure cli before interacting with hub") @@ -38,7 +38,7 @@ cscli scenarios remove crowdsecurity/ssh-bf } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { - log.Infoln("Run 'sudo cscli hub update' to get the hub index") + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) } diff --git a/cmd/crowdsec-cli/simulation.go b/cmd/crowdsec-cli/simulation.go index eef48798c..c605be762 100644 --- a/cmd/crowdsec-cli/simulation.go +++ b/cmd/crowdsec-cli/simulation.go @@ -134,11 +134,11 @@ cscli simulation disable crowdsecurity/ssh-bf`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") log.Fatalf("Failed to get Hub index : %v", err) - log.Infoln("Run 'sudo cscli hub update' to get the hub index") } if len(args) > 0 { @@ -153,7 +153,7 @@ cscli simulation disable crowdsecurity/ssh-bf`, } isExcluded := inSlice(scenario, csConfig.Cscli.SimulationConfig.Exclusions) if *csConfig.Cscli.SimulationConfig.Simulation && !isExcluded { - log.Warningf("global simulation is already enabled") + log.Warning("global simulation is already enabled") continue } if !*csConfig.Cscli.SimulationConfig.Simulation && isExcluded { @@ -162,13 +162,13 @@ cscli simulation disable crowdsecurity/ssh-bf`, } if *csConfig.Cscli.SimulationConfig.Simulation && isExcluded { if err := removeFromExclusion(scenario); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } log.Printf("simulation enabled for '%s'", scenario) continue } if err := addToExclusion(scenario); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } log.Printf("simulation mode for '%s' enabled", scenario) } @@ -202,7 +202,7 @@ cscli simulation disable crowdsecurity/ssh-bf`, } if !*csConfig.Cscli.SimulationConfig.Simulation && isExcluded { if err := removeFromExclusion(scenario); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } log.Printf("simulation mode for '%s' disabled", scenario) continue @@ -212,7 +212,7 @@ cscli simulation disable crowdsecurity/ssh-bf`, continue } if err := addToExclusion(scenario); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } log.Printf("simulation mode for '%s' disabled", scenario) } @@ -238,7 +238,7 @@ cscli simulation disable crowdsecurity/ssh-bf`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { if err := simulationStatus(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } }, PersistentPostRun: func(cmd *cobra.Command, args []string) { diff --git a/cmd/crowdsec-cli/utils.go b/cmd/crowdsec-cli/utils.go index c3e120273..6f7e7ccff 100644 --- a/cmd/crowdsec-cli/utils.go +++ b/cmd/crowdsec-cli/utils.go @@ -51,7 +51,7 @@ func indexOf(s string, slice []string) int { func LoadHub() error { if err := csConfig.LoadHub(); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } if csConfig.Hub == nil { return fmt.Errorf("unable to load hub") diff --git a/cmd/crowdsec/api.go b/cmd/crowdsec/api.go index e6ff17c84..0fb04e85d 100644 --- a/cmd/crowdsec/api.go +++ b/cmd/crowdsec/api.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "runtime" "github.com/crowdsecurity/crowdsec/pkg/apiserver" @@ -21,17 +20,17 @@ func initAPIServer(cConfig *csconfig.Config) (*apiserver.APIServer, error) { log.Info("initiating plugin broker") //On windows, the plugins are always run as medium-integrity processes, so we don't care about plugin_config if cConfig.PluginConfig == nil && runtime.GOOS != "windows" { - return nil, fmt.Errorf("plugins are enabled, but the plugin_config section is missing in the configuration") + return nil, errors.New("plugins are enabled, but the plugin_config section is missing in the configuration") } if cConfig.ConfigPaths.NotificationDir == "" { - return nil, fmt.Errorf("plugins are enabled, but config_paths.notification_dir is not defined") + return nil, errors.New("plugins are enabled, but config_paths.notification_dir is not defined") } if cConfig.ConfigPaths.PluginDir == "" { - return nil, fmt.Errorf("plugins are enabled, but config_paths.plugin_dir is not defined") + return nil, errors.New("plugins are enabled, but config_paths.plugin_dir is not defined") } err = pluginBroker.Init(cConfig.PluginConfig, cConfig.API.Server.Profiles, cConfig.ConfigPaths) if err != nil { - return nil, fmt.Errorf("unable to run local API: %s", err) + return nil, errors.Wrap(err, "unable to run local API") } log.Info("initiated plugin broker") apiServer.AttachPluginBroker(&pluginBroker) diff --git a/cmd/crowdsec/configfile.go b/cmd/crowdsec/configfile.go deleted file mode 100644 index 76bd159f1..000000000 --- a/cmd/crowdsec/configfile.go +++ /dev/null @@ -1,5 +0,0 @@ -// +build linux freebsd netbsd openbsd solaris !windows - -package main - -const DefaultConfigFile = "/etc/crowdsec/config.yaml" diff --git a/cmd/crowdsec/configfile_windows.go b/cmd/crowdsec/configfile_windows.go deleted file mode 100644 index 91d316e29..000000000 --- a/cmd/crowdsec/configfile_windows.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -const DefaultConfigFile = "C:\\ProgramData\\CrowdSec\\config\\config.yaml" diff --git a/cmd/crowdsec/main.go b/cmd/crowdsec/main.go index 3587f13bd..b88a84f5e 100644 --- a/cmd/crowdsec/main.go +++ b/cmd/crowdsec/main.go @@ -10,6 +10,7 @@ import ( _ "net/http/pprof" "time" + "github.com/confluentinc/bincover" "github.com/crowdsecurity/crowdsec/pkg/acquisition" "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/csplugin" @@ -196,7 +197,6 @@ func (f *Flags) Parse() { // LoadConfig returns a configuration parsed from configuration file func LoadConfig(cConfig *csconfig.Config) error { - if dumpFolder != "" { parser.ParseDump = true parser.DumpFolder = dumpFolder @@ -217,11 +217,11 @@ func LoadConfig(cConfig *csconfig.Config) error { } if !cConfig.DisableAgent && (cConfig.API == nil || cConfig.API.Client == nil || cConfig.API.Client.Credentials == nil) { - log.Fatalf("missing local API credentials for crowdsec agent, abort") + return errors.New("missing local API credentials for crowdsec agent, abort") } if cConfig.DisableAPI && cConfig.DisableAgent { - log.Fatalf("You must run at least the API Server or crowdsec") + return errors.New("You must run at least the API Server or crowdsec") } if flags.DebugLevel { @@ -260,8 +260,26 @@ func LoadConfig(cConfig *csconfig.Config) error { return nil } -func main() { +// This must be called right before the program termination, to allow +// measuring functional test coverage in case of abnormal exit. +// +// without bincover: log error and exit with code +// with bincover: log error and tell bincover the exit code, then return +func exitWithCode(exitCode int, err error) { + if err != nil { + // this method of logging a fatal error does not + // trigger a program exit (as stated by the authors, it + // is not going to change in logrus to keep backward + // compatibility), and allows us to report coverage. + log.NewEntry(log.StandardLogger()).Log(log.FatalLevel, err) + } + if bincoverTesting == "" { + os.Exit(exitCode) + } + bincover.ExitCode = exitCode +} +func main() { defer types.CatchPanic("crowdsec/main") log.Debugf("os.Args: %v", os.Args) @@ -269,9 +287,25 @@ func main() { // Handle command line arguments flags = &Flags{} flags.Parse() + + if len(flag.Args()) > 0 { + fmt.Fprintf(os.Stderr, "argument provided but not defined: %s\n", flag.Args()[0]) + flag.Usage() + // the flag package exits with 2 in case of unknown flag + exitWithCode(2, nil) + return + } + if flags.PrintVersion { cwversion.Show() - os.Exit(0) + exitWithCode(0, nil) + return } - StartRunSvc() + + exitCode := 0 + err := StartRunSvc() + if err != nil { + exitCode = 1 + } + exitWithCode(exitCode, err) } diff --git a/cmd/crowdsec/run_in_svc.go b/cmd/crowdsec/run_in_svc.go index cf70f537a..55fb8e810 100644 --- a/cmd/crowdsec/run_in_svc.go +++ b/cmd/crowdsec/run_in_svc.go @@ -6,7 +6,6 @@ package main import ( "os" - "github.com/confluentinc/bincover" "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/cwversion" "github.com/crowdsecurity/crowdsec/pkg/types" @@ -14,7 +13,7 @@ import ( "github.com/sirupsen/logrus/hooks/writer" ) -func StartRunSvc() { +func StartRunSvc() error { var ( cConfig *csconfig.Config err error @@ -30,10 +29,10 @@ func StartRunSvc() { cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI) if err != nil { - log.Fatalf(err.Error()) + return err } if err := LoadConfig(cConfig); err != nil { - log.Fatalf(err.Error()) + return err } // Configure logging if err = types.SetDefaultLoggerConfig(cConfig.Common.LogMedia, cConfig.Common.LogDir, *cConfig.Common.LogLevel, @@ -51,18 +50,5 @@ func StartRunSvc() { if cConfig.Prometheus != nil { go registerPrometheus(cConfig.Prometheus) } - - if exitCode, err := Serve(cConfig); err != nil { - if err != nil { - // this method of logging a fatal error does not - // trigger a program exit (as stated by the authors, it - // is not going to change in logrus to keep backward - // compatibility), and allows us to report coverage. - log.NewEntry(log.StandardLogger()).Log(log.FatalLevel, err) - if bincoverTesting == "" { - os.Exit(exitCode) - } - bincover.ExitCode = exitCode - } - } + return Serve(cConfig) } diff --git a/cmd/crowdsec/run_in_svc_windows.go b/cmd/crowdsec/run_in_svc_windows.go index 1f23dba8e..ff97e3c20 100644 --- a/cmd/crowdsec/run_in_svc_windows.go +++ b/cmd/crowdsec/run_in_svc_windows.go @@ -1,61 +1,59 @@ package main import ( + "fmt" "os" - "github.com/confluentinc/bincover" "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/cwversion" "github.com/crowdsecurity/crowdsec/pkg/types" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/writer" "golang.org/x/sys/windows/svc" ) -func StartRunSvc() { - +func StartRunSvc() error { const svcName = "CrowdSec" const svcDescription = "Crowdsec IPS/IDS" isRunninginService, err := svc.IsWindowsService() if err != nil { - log.Fatalf("failed to determine if we are running in windows service mode: %v", err) + return errors.Wrap(err, "failed to determine if we are running in windows service mode") } if isRunninginService { - runService(svcName) - return + return runService(svcName) } if flags.WinSvc == "Install" { err = installService(svcName, svcDescription) if err != nil { - log.Fatalf("failed to %s %s: %v", flags.WinSvc, svcName, err) + return errors.Wrapf(err, "failed to %s %s", flags.WinSvc, svcName) } } else if flags.WinSvc == "Remove" { err = removeService(svcName) if err != nil { - log.Fatalf("failed to %s %s: %v", flags.WinSvc, svcName, err) + return errors.Wrapf(err, "failed to %s %s", flags.WinSvc, svcName) } } else if flags.WinSvc == "Start" { err = startService(svcName) if err != nil { - log.Fatalf("failed to %s %s: %v", flags.WinSvc, svcName, err) + return errors.Wrapf(err, "failed to %s %s", flags.WinSvc, svcName) } } else if flags.WinSvc == "Stop" { err = controlService(svcName, svc.Stop, svc.Stopped) if err != nil { - log.Fatalf("failed to %s %s: %v", flags.WinSvc, svcName, err) + return errors.Wrapf(err, "failed to %s %s", flags.WinSvc, svcName) } } else if flags.WinSvc == "" { - WindowsRun() + return WindowsRun() } else { - log.Fatalf("Invalid value for winsvc parameter: %s", flags.WinSvc) + return fmt.Errorf("Invalid value for winsvc parameter: %s", flags.WinSvc) } - + return nil } -func WindowsRun() { - +func WindowsRun() error { var ( cConfig *csconfig.Config err error @@ -71,15 +69,15 @@ func WindowsRun() { cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI) if err != nil { - log.Fatalf(err.Error()) + return err } if err := LoadConfig(cConfig); err != nil { - log.Fatalf(err.Error()) + return err } // Configure logging if err = types.SetDefaultLoggerConfig(cConfig.Common.LogMedia, cConfig.Common.LogDir, *cConfig.Common.LogLevel, cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles, cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs); err != nil { - log.Fatal(err.Error()) + return err } log.Infof("Crowdsec %s", cwversion.VersionStr()) @@ -92,18 +90,5 @@ func WindowsRun() { if cConfig.Prometheus != nil { go registerPrometheus(cConfig.Prometheus) } - - if exitCode, err := Serve(cConfig); err != nil { - if err != nil { - // this method of logging a fatal error does not - // trigger a program exit (as stated by the authors, it - // is not going to change in logrus to keep backward - // compatibility), and allows us to report coverage. - log.NewEntry(log.StandardLogger()).Log(log.FatalLevel, err) - if bincoverTesting != "" { - os.Exit(exitCode) - } - bincover.ExitCode = exitCode - } - } + return Serve(cConfig) } diff --git a/cmd/crowdsec/serve.go b/cmd/crowdsec/serve.go index 1b50d93fb..4e540de2f 100644 --- a/cmd/crowdsec/serve.go +++ b/cmd/crowdsec/serve.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "os/signal" "syscall" @@ -18,7 +17,7 @@ import ( //"github.com/sevlyar/go-daemon" ) -// debugHandler is kept as a dev convenience : it shuts down and serialize internal state +//nolint: deadcode,unused // debugHandler is kept as a dev convenience : it shuts down and serialize internal state func debugHandler(sig os.Signal, cConfig *csconfig.Config) error { var tmpFile string var err error @@ -53,22 +52,22 @@ func reloadHandler(sig os.Signal, cConfig *csconfig.Config) error { cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI) if err != nil { - log.Fatalf(err.Error()) + return err } if err := LoadConfig(cConfig); err != nil { - log.Fatalf(err.Error()) + return err } // Configure logging if err = types.SetDefaultLoggerConfig(cConfig.Common.LogMedia, cConfig.Common.LogDir, *cConfig.Common.LogLevel, cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles, cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs); err != nil { - log.Fatal(err.Error()) + return err } if !cConfig.DisableAPI { apiServer, err := initAPIServer(cConfig) if err != nil { - return fmt.Errorf("unable to init api server: %s", err) + return errors.Wrap(err, "unable to init api server") } serveAPIServer(apiServer) @@ -77,7 +76,7 @@ func reloadHandler(sig os.Signal, cConfig *csconfig.Config) error { if !cConfig.DisableAgent { csParsers, err := initCrowdsec(cConfig) if err != nil { - return fmt.Errorf("unable to init crowdsec: %s", err) + return errors.Wrap(err, "unable to init crowdsec") } //restore bucket state if tmpFile != "" { @@ -164,20 +163,18 @@ func shutdownCrowdsec() error { func shutdown(sig os.Signal, cConfig *csconfig.Config) error { if !cConfig.DisableAgent { if err := shutdownCrowdsec(); err != nil { - log.Errorf("Failed to shut down crowdsec: %s", err) - return err + return errors.Wrap(err, "Failed to shut down crowdsec") } } if !cConfig.DisableAPI { if err := shutdownAPI(); err != nil { - log.Errorf("Failed to shut down api routines: %s", err) - return err + return errors.Wrap(err, "Failed to shut down api routines") } } return nil } -func HandleSignals(cConfig *csconfig.Config) int { +func HandleSignals(cConfig *csconfig.Config) error { signalChan := make(chan os.Signal, 1) //We add os.Interrupt mostly to ease windows dev, it allows to simulate a clean shutdown when running in the console signal.Notify(signalChan, @@ -185,9 +182,10 @@ func HandleSignals(cConfig *csconfig.Config) int { syscall.SIGTERM, os.Interrupt) - exitChan := make(chan int) + exitChan := make(chan error) go func() { defer types.CatchPanic("crowdsec/HandleSignals") + Loop: for { s := <-signalChan switch s { @@ -195,28 +193,33 @@ func HandleSignals(cConfig *csconfig.Config) int { case syscall.SIGHUP: log.Warningf("SIGHUP received, reloading") if err := shutdown(s, cConfig); err != nil { - log.Fatalf("failed shutdown : %s", err) + exitChan <- errors.Wrap(err, "failed shutdown") + break Loop } if err := reloadHandler(s, cConfig); err != nil { - log.Fatalf("Reload handler failure : %s", err) + exitChan <- errors.Wrap(err, "reload handler failure") + break Loop } // ctrl+C, kill -SIGINT XXXX, kill -SIGTERM XXXX case os.Interrupt, syscall.SIGTERM: log.Warningf("SIGTERM received, shutting down") if err := shutdown(s, cConfig); err != nil { - log.Fatalf("failed shutdown : %s", err) + exitChan <- errors.Wrap(err, "failed shutdown") + break Loop } - exitChan <- 0 + exitChan <- nil } } }() - code := <-exitChan - log.Warningf("Crowdsec service shutting down") - return code + err := <-exitChan + if err == nil { + log.Warningf("Crowdsec service shutting down") + } + return err } -func Serve(cConfig *csconfig.Config) (int, error) { +func Serve(cConfig *csconfig.Config) error { acquisTomb = tomb.Tomb{} parsersTomb = tomb.Tomb{} bucketsTomb = tomb.Tomb{} @@ -227,7 +230,7 @@ func Serve(cConfig *csconfig.Config) (int, error) { if !cConfig.DisableAPI { apiServer, err := initAPIServer(cConfig) if err != nil { - return 1, errors.Wrap(err, "api server init") + return errors.Wrap(err, "api server init") } if !flags.TestMode { serveAPIServer(apiServer) @@ -237,7 +240,7 @@ func Serve(cConfig *csconfig.Config) (int, error) { if !cConfig.DisableAgent { csParsers, err := initCrowdsec(cConfig) if err != nil { - return 1, errors.Wrap(err, "crowdsec init") + return errors.Wrap(err, "crowdsec init") } /* if it's just linting, we're done */ if !flags.TestMode { @@ -256,7 +259,7 @@ func Serve(cConfig *csconfig.Config) (int, error) { log.Errorf("Failed to notify(sent: %v): %v", sent, err) } /*wait for signals*/ - return HandleSignals(cConfig), nil + return HandleSignals(cConfig) } for { diff --git a/cmd/crowdsec/win_service.go b/cmd/crowdsec/win_service.go index bfd7f4048..36beed776 100644 --- a/cmd/crowdsec/win_service.go +++ b/cmd/crowdsec/win_service.go @@ -12,6 +12,7 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/types" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/sys/windows/svc" ) @@ -61,27 +62,29 @@ loop: return } -func runService(name string) { +func runService(name string) error { cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI) if err != nil { - log.Fatalf(err.Error()) + return err } - if err := LoadConfig(cConfig); err != nil { - log.Fatalf(err.Error()) - } - // Configure logging - if err = types.SetDefaultLoggerConfig(cConfig.Common.LogMedia, cConfig.Common.LogDir, *cConfig.Common.LogLevel, - cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles, cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs); err != nil { - log.Fatal(err.Error()) - } - log.Infof("starting %s service", name) + if err := LoadConfig(cConfig); err != nil { + return err + } + + // Configure logging + if err := types.SetDefaultLoggerConfig(cConfig.Common.LogMedia, cConfig.Common.LogDir, *cConfig.Common.LogLevel, + cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles, cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs); err != nil { + return err + } + + log.Infof("starting %s service", name) winsvc := crowdsec_winservice{config: cConfig} - err = svc.Run(name, &winsvc) - if err != nil { - log.Errorf("%s service failed: %s", name, err) - return + if err := svc.Run(name, &winsvc); err != nil { + return errors.Wrapf(err, "%s service failed", name) } + log.Infof("%s service stopped", name) + return nil } diff --git a/pkg/acquisition/acquisition_test.go b/pkg/acquisition/acquisition_test.go index 282aa6841..9e95463ee 100644 --- a/pkg/acquisition/acquisition_test.go +++ b/pkg/acquisition/acquisition_test.go @@ -493,6 +493,7 @@ READLOOP: } } +//nolint: structcheck,unused type MockSourceByDSN struct { configuration.DataSourceCommonCfg `yaml:",inline"` Toto string `yaml:"toto"` diff --git a/pkg/acquisition/modules/file/file.go b/pkg/acquisition/modules/file/file.go index 6128d3a16..e9f9bb189 100644 --- a/pkg/acquisition/modules/file/file.go +++ b/pkg/acquisition/modules/file/file.go @@ -137,14 +137,14 @@ func (f *FileSource) ConfigureByDSN(dsn string, labels map[string]string, logger if len(args) == 2 && len(args[1]) != 0 { params, err := url.ParseQuery(args[1]) if err != nil { - return fmt.Errorf("could not parse file args : %s", err) + return errors.Wrap(err, "could not parse file args") } for key, value := range params { if key != "log_level" { return fmt.Errorf("unsupported key %s in file DSN", key) } if len(value) != 1 { - return fmt.Errorf("expected zero or one value for 'log_level'") + return errors.New("expected zero or one value for 'log_level'") } lvl, err := log.ParseLevel(value[0]) if err != nil { @@ -351,7 +351,6 @@ func (f *FileSource) tailFile(out chan types.Event, t *tomb.Tomb, tail *tail.Tai logger := f.logger.WithField("tail", tail.Filename) logger.Debugf("-> Starting tail of %s", tail.Filename) for { - l := types.Line{} select { case <-t.Dying(): logger.Infof("File datasource %s stopping", tail.Filename) @@ -377,19 +376,22 @@ func (f *FileSource) tailFile(out chan types.Event, t *tomb.Tomb, tail *tail.Tai continue } linesRead.With(prometheus.Labels{"source": tail.Filename}).Inc() - l.Raw = trimLine(line.Text) - l.Labels = f.config.Labels - l.Time = line.Time - l.Src = tail.Filename - l.Process = true - l.Module = f.GetName() + l := types.Line{ + Raw: trimLine(line.Text), + Labels: f.config.Labels, + Time: line.Time, + Src: tail.Filename, + Process: true, + Module: f.GetName(), + } //we're tailing, it must be real time logs logger.Debugf("pushing %+v", l) - if !f.config.UseTimeMachine { - out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: leaky.LIVE} - } else { - out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: leaky.TIMEMACHINE} + + expectMode := leaky.LIVE + if f.config.UseTimeMachine { + expectMode = leaky.TIMEMACHINE } + out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: expectMode} } } } @@ -421,14 +423,15 @@ func (f *FileSource) readFile(filename string, out chan types.Event, t *tomb.Tom if scanner.Text() == "" { continue } - logger.Debugf("line %s", scanner.Text()) - l := types.Line{} - l.Raw = scanner.Text() - l.Time = time.Now().UTC() - l.Src = filename - l.Labels = f.config.Labels - l.Process = true - l.Module = f.GetName() + l := types.Line{ + Raw: scanner.Text(), + Time: time.Now().UTC(), + Src: filename, + Labels: f.config.Labels, + Process: true, + Module: f.GetName(), + } + logger.Debugf("line %s", l.Raw) linesRead.With(prometheus.Labels{"source": filename}).Inc() //we're reading logs at once, it must be time-machine buckets diff --git a/pkg/apiclient/alerts_service_test.go b/pkg/apiclient/alerts_service_test.go index 096268bfe..3cc7cc3cc 100644 --- a/pkg/apiclient/alerts_service_test.go +++ b/pkg/apiclient/alerts_service_test.go @@ -37,7 +37,7 @@ func TestAlertsListAsMachine(t *testing.T) { }) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } defer teardown() @@ -240,7 +240,7 @@ func TestAlertsGetAsMachine(t *testing.T) { }) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } defer teardown() @@ -431,7 +431,7 @@ func TestAlertsCreateAsMachine(t *testing.T) { }) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } defer teardown() @@ -475,7 +475,7 @@ func TestAlertsDeleteAsMachine(t *testing.T) { }) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } defer teardown() diff --git a/pkg/apiclient/auth_service_test.go b/pkg/apiclient/auth_service_test.go index 107e3d685..4cee22d92 100644 --- a/pkg/apiclient/auth_service_test.go +++ b/pkg/apiclient/auth_service_test.go @@ -57,7 +57,7 @@ func TestWatcherAuth(t *testing.T) { client, err := NewClient(mycfg) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } _, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{ @@ -81,7 +81,7 @@ func TestWatcherAuth(t *testing.T) { client, err = NewClient(mycfg) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } _, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{ @@ -171,7 +171,7 @@ func TestWatcherUnregister(t *testing.T) { client, err := NewClient(mycfg) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } _, err = client.Auth.UnregisterWatcher(context.Background()) if err != nil { @@ -225,7 +225,7 @@ func TestWatcherEnroll(t *testing.T) { client, err := NewClient(mycfg) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } _, err = client.Auth.EnrollWatcher(context.Background(), "goodkey", "", []string{}, false) diff --git a/pkg/apiclient/auth_test.go b/pkg/apiclient/auth_test.go index 2817fae77..123f9ef28 100644 --- a/pkg/apiclient/auth_test.go +++ b/pkg/apiclient/auth_test.go @@ -41,7 +41,7 @@ func TestApiAuth(t *testing.T) { newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client()) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } alert := DecisionsListOpts{IPEquals: new(string)} diff --git a/pkg/apiclient/decisions_service_test.go b/pkg/apiclient/decisions_service_test.go index 81a2bd0e3..fb668ce90 100644 --- a/pkg/apiclient/decisions_service_test.go +++ b/pkg/apiclient/decisions_service_test.go @@ -46,7 +46,7 @@ func TestDecisionsList(t *testing.T) { newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client()) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } tduration := "3h59m55.756182786s" @@ -77,7 +77,7 @@ func TestDecisionsList(t *testing.T) { } if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } if !reflect.DeepEqual(*decisions, *expected) { t.Fatalf("returned %+v, want %+v", resp, expected) @@ -137,7 +137,7 @@ func TestDecisionsStream(t *testing.T) { newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client()) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } tduration := "3h59m55.756182786s" @@ -168,7 +168,7 @@ func TestDecisionsStream(t *testing.T) { } if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } if !reflect.DeepEqual(*decisions, *expected) { t.Fatalf("returned %+v, want %+v", resp, expected) @@ -220,7 +220,7 @@ func TestDeleteDecisions(t *testing.T) { }) if err != nil { - log.Fatalf("new api client: %s", err.Error()) + log.Fatalf("new api client: %s", err) } filters := DecisionsDeleteOpts{IPEquals: new(string)} diff --git a/pkg/apiserver/api_key_test.go b/pkg/apiserver/api_key_test.go index 3022c9584..0f278673e 100644 --- a/pkg/apiserver/api_key_test.go +++ b/pkg/apiserver/api_key_test.go @@ -18,7 +18,7 @@ func TestAPIKey(t *testing.T) { APIKey, err := CreateTestBouncer(config.API.Server.DbConfig) if err != nil { - log.Fatalf("%s", err.Error()) + log.Fatal(err) } // Login with empty token w := httptest.NewRecorder() diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 942cc4c99..1cc32b570 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -276,11 +276,11 @@ func (s *APIServer) Run() error { go func() { if s.TLS != nil && s.TLS.CertFilePath != "" && s.TLS.KeyFilePath != "" { if err := s.httpServer.ListenAndServeTLS(s.TLS.CertFilePath, s.TLS.KeyFilePath); err != nil { - log.Fatalf(err.Error()) + log.Fatal(err) } } else { if err := s.httpServer.ListenAndServe(); err != http.ErrServerClosed { - log.Fatalf(err.Error()) + log.Fatal(err) } } }() diff --git a/pkg/apiserver/machines_test.go b/pkg/apiserver/machines_test.go index b9d63c172..6dfe6c108 100644 --- a/pkg/apiserver/machines_test.go +++ b/pkg/apiserver/machines_test.go @@ -38,7 +38,7 @@ func TestCreateMachine(t *testing.T) { // Create machine b, err := json.Marshal(MachineTest) if err != nil { - log.Fatalf("unable to marshal MachineTest") + log.Fatal("unable to marshal MachineTest") } body := string(b) @@ -61,7 +61,7 @@ func TestCreateMachineWithForwardedFor(t *testing.T) { // Create machine b, err := json.Marshal(MachineTest) if err != nil { - log.Fatalf("unable to marshal MachineTest") + log.Fatal("unable to marshal MachineTest") } body := string(b) @@ -90,7 +90,7 @@ func TestCreateMachineWithForwardedForNoConfig(t *testing.T) { // Create machine b, err := json.Marshal(MachineTest) if err != nil { - log.Fatalf("unable to marshal MachineTest") + log.Fatal("unable to marshal MachineTest") } body := string(b) @@ -121,7 +121,7 @@ func TestCreateMachineWithoutForwardedFor(t *testing.T) { // Create machine b, err := json.Marshal(MachineTest) if err != nil { - log.Fatalf("unable to marshal MachineTest") + log.Fatal("unable to marshal MachineTest") } body := string(b) diff --git a/pkg/apiserver/middlewares/v1/jwt.go b/pkg/apiserver/middlewares/v1/jwt.go index 21b178227..51b82db82 100644 --- a/pkg/apiserver/middlewares/v1/jwt.go +++ b/pkg/apiserver/middlewares/v1/jwt.go @@ -138,29 +138,42 @@ func Unauthorized(c *gin.Context, code int, message string) { }) } +func randomSecret() ([]byte, error) { + size := 64 + secret := make([]byte, size) + + n, err := rand.Read(secret) + if err != nil { + return nil, errors.New("unable to generate a new random seed for JWT generation") + } + + if n != size { + return nil, errors.New("not enough entropy at random seed generation for JWT generation") + } + + return secret, nil +} + func NewJWT(dbClient *database.Client) (*JWT, error) { // Get secret from environment variable "SECRET" var ( secret []byte + err error ) - //Please be aware that brute force HS256 is possible. - //PLEASE choose a STRONG secret - secret_string := os.Getenv("CS_LAPI_SECRET") - if secret_string == "" { - secret = make([]byte, 64) - if n, err := rand.Read(secret); err != nil { - log.Fatalf("unable to generate a new random seed for JWT generation") - } else { - if n != 64 { - log.Fatalf("not enough entropy at random seed generation for JWT generation") - } - } - } else { - secret = []byte(secret_string) - if len(secret) < 64 { - log.Fatalf("secret not strong enough") + // Please be aware that brute force HS256 is possible. + // PLEASE choose a STRONG secret + secretString := os.Getenv("CS_LAPI_SECRET") + secret = []byte(secretString) + + switch l := len(secret); { + case l == 0: + secret, err = randomSecret() + if err != nil { + return &JWT{}, err } + case l < 64: + return &JWT{}, errors.New("CS_LAPI_SECRET not strong enough") } jwtMiddleware := &JWT{ diff --git a/pkg/apiserver/testutils.go b/pkg/apiserver/testutils.go deleted file mode 100644 index ba2f510df..000000000 --- a/pkg/apiserver/testutils.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package apiserver - -import "os" - -func cleanFile(path string) { - os.Remove(path) -} diff --git a/pkg/apiserver/testutils_windows.go b/pkg/apiserver/testutils_windows.go deleted file mode 100644 index f322ee200..000000000 --- a/pkg/apiserver/testutils_windows.go +++ /dev/null @@ -1,7 +0,0 @@ -package apiserver - -import "os" - -func cleanFile(path string) { - os.Remove(path) -} diff --git a/pkg/csconfig/crowdsec_service.go b/pkg/csconfig/crowdsec_service.go index 56cc2fb22..7498d2e06 100644 --- a/pkg/csconfig/crowdsec_service.go +++ b/pkg/csconfig/crowdsec_service.go @@ -114,7 +114,7 @@ func (c *Config) LoadCrowdsec() error { return fmt.Errorf("loading api client: %s", err.Error()) } if err := c.LoadHub(); err != nil { - return fmt.Errorf("loading hub: %s", err) + return errors.Wrap(err, "while loading hub") } return nil } diff --git a/pkg/csconfig/hub_test.go b/pkg/csconfig/hub_test.go index 2773a53c9..136790d5f 100644 --- a/pkg/csconfig/hub_test.go +++ b/pkg/csconfig/hub_test.go @@ -12,22 +12,22 @@ import ( func TestLoadHub(t *testing.T) { hubFullPath, err := filepath.Abs("./hub") if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } dataFullPath, err := filepath.Abs("./data") if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } configDirFullPath, err := filepath.Abs("./tests") if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } hubIndexFileFullPath, err := filepath.Abs("./hub/.index.json") if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } tests := []struct { diff --git a/pkg/cwhub/cwhub_test.go b/pkg/cwhub/cwhub_test.go index cd42a76da..68da87ba7 100644 --- a/pkg/cwhub/cwhub_test.go +++ b/pkg/cwhub/cwhub_test.go @@ -133,7 +133,6 @@ func TestGetters(t *testing.T) { } func TestIndexDownload(t *testing.T) { - cfg := test_prepenv() err := UpdateHubIdx(cfg.Hub) diff --git a/pkg/parser/parsing_test.go b/pkg/parser/parsing_test.go index bcf39198e..0f3777258 100644 --- a/pkg/parser/parsing_test.go +++ b/pkg/parser/parsing_test.go @@ -295,7 +295,7 @@ func testSubSet(testSet TestFile, pctx UnixParserCtx, nodes []Node) (bool, error only the keys of the expected part are checked against result */ if len(testSet.Results) == 0 && len(results) == 0 { - log.Fatalf("No results, no tests, abort.") + log.Fatal("No results, no tests, abort.") return false, fmt.Errorf("no tests, no results") } diff --git a/pkg/parser/runtime.go b/pkg/parser/runtime.go index 620cb0195..5fc8fe88a 100644 --- a/pkg/parser/runtime.go +++ b/pkg/parser/runtime.go @@ -187,7 +187,7 @@ func (n *Node) ProcessStatics(statics []types.ExtraField, event *types.Event) er clog.Debugf("%s = '%s'", static.TargetByName, value) } } else { - clog.Fatalf("unable to process static : unknown tartget") + clog.Fatal("unable to process static : unknown target") } } diff --git a/tests/bats/01_base.bats b/tests/bats/01_base.bats index 18ab07268..1bdfd0306 100644 --- a/tests/bats/01_base.bats +++ b/tests/bats/01_base.bats @@ -13,6 +13,7 @@ teardown_file() { setup() { load "../lib/setup.sh" + load "../lib/bats-file/load.bash" ./instance-data load ./instance-crowdsec start } @@ -139,15 +140,45 @@ declare stderr assert_output "127.0.0.1:8080" } -@test "${FILE} cscli config backup" { +@test "${FILE} cscli config backup / restore" { + # test that we need a valid path + # disabled because in CI, the empty string is not passed as a parameter + ## run -1 --separate-stderr cscli config backup "" + ## run -0 echo "${stderr}" + ## assert_output --partial "Failed to backup configurations: directory path can't be empty" + + run -1 --separate-stderr cscli config backup "/dev/null/blah" + run -0 echo "${stderr}" + assert_output --partial "Failed to backup configurations: while creating /dev/null/blah: mkdir /dev/null/blah: not a directory" + + # pick a dirpath backupdir=$(TMPDIR="${BATS_TEST_TMPDIR}" mktemp -u) + + # succeed the first time run -0 cscli config backup "${backupdir}" assert_output --partial "Starting configuration backup" - run -1 --separate-stderr cscli config backup "${backupdir}" + # don't overwrite an existing backup + run -1 --separate-stderr cscli config backup "${backupdir}" run -0 echo "${stderr}" assert_output --partial "Failed to backup configurations" assert_output --partial "file exists" + + SIMULATION_YAML="$(config_yq '.config_paths.simulation_path')" + + # restore + rm "${SIMULATION_YAML}" + run -0 cscli config restore "${backupdir}" + assert_file_exist "${SIMULATION_YAML}" + + # cleanup + rm -rf -- "${backupdir:?}" + + # backup: detect missing files + rm "${SIMULATION_YAML}" + run -1 --separate-stderr cscli config backup "${backupdir}" + run -0 echo "${stderr}" + assert_output --regexp "Failed to backup configurations: failed copy .* to .*: stat .*: no such file or directory" rm -rf -- "${backupdir:?}" } @@ -244,12 +275,14 @@ declare stderr assert_output --partial "# bash completion for cscli" run -0 cscli completion zsh assert_output --partial "# zsh completion for cscli" + run -0 cscli completion powershell + assert_output --partial "# powershell completion for cscli" + run -0 cscli completion fish + assert_output --partial "# fish completion for cscli" rm "${CONFIG_YAML}" run -0 cscli completion bash assert_output --partial "# bash completion for cscli" - run -0 cscli completion zsh - assert_output --partial "# zsh completion for cscli" } @test "${FILE} cscli hub list" { diff --git a/tests/bats/01_crowdsec.bats b/tests/bats/01_crowdsec.bats new file mode 100644 index 000000000..6c482cbdd --- /dev/null +++ b/tests/bats/01_crowdsec.bats @@ -0,0 +1,57 @@ +#!/usr/bin/env bats +# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si: + +set -u + +setup_file() { + load "../lib/setup_file.sh" +} + +teardown_file() { + load "../lib/teardown_file.sh" +} + +setup() { + load "../lib/setup.sh" + ./instance-data load +} + +teardown() { + ./instance-crowdsec stop +} + +# to silence shellcheck +declare stderr + +#---------- + +@test "${FILE} crowdsec (usage)" { + run -0 --separate-stderr timeout 2s "${CROWDSEC}" -h + run -0 echo "${stderr}" + assert_line --regexp "Usage of .*:" + + run -0 --separate-stderr timeout 2s "${CROWDSEC}" --help + run -0 echo "${stderr}" + assert_line --regexp "Usage of .*:" +} + +@test "${FILE} crowdsec (unknown flag)" { + run -2 --separate-stderr timeout 2s "${CROWDSEC}" --foobar + run -0 echo "${stderr}" + assert_line "flag provided but not defined: -foobar" + assert_line --regexp "Usage of .*" +} + +@test "${FILE} crowdsec (unknown argument)" { + run -2 --separate-stderr timeout 2s "${CROWDSEC}" trololo + run -0 echo "${stderr}" + assert_line "argument provided but not defined: trololo" + assert_line --regexp "Usage of .*" +} + +@test "${FILE} crowdsec (no api and no agent)" { + run -1 --separate-stderr timeout 2s "${CROWDSEC}" -no-api -no-cs + run -0 echo "${stderr}" + assert_line --partial "You must run at least the API Server or crowdsec" +} + diff --git a/tests/bats/06_crowdsec.bats b/tests/bats/06_crowdsec.bats index a26d8927c..626a078b7 100755 --- a/tests/bats/06_crowdsec.bats +++ b/tests/bats/06_crowdsec.bats @@ -34,3 +34,8 @@ declare stderr assert_output --partial "api server init: unable to run local API: unable to init database client: unknown database type 'meh'" } +@test "${FILE} CS_LAPI_SECRET not strong enough" { + CS_LAPI_SECRET=foo run -1 --separate-stderr timeout 2s "${CROWDSEC}" + run -0 echo "${stderr}" + assert_output --partial "api server init: unable to run local API: CS_LAPI_SECRET not strong enough" +} diff --git a/tests/bats/72_plugin_badconfig.bats b/tests/bats/72_plugin_badconfig.bats index f24bfebb2..db3eae271 100644 --- a/tests/bats/72_plugin_badconfig.bats +++ b/tests/bats/72_plugin_badconfig.bats @@ -86,3 +86,35 @@ teardown() { assert_output --partial "api server init: unable to run local API: while loading plugin: plugin at ${PLUGIN_DIR}/notification-http is world writable, world writable plugins are invalid" } +@test "${FILE} config.yaml: missing .plugin_config section" { + yq e 'del(.plugin_config)' -i "${CONFIG_YAML}" + yq e '.notifications=["http_default"]' -i "${PROFILES_PATH}" + run -1 --separate-stderr timeout 2s "${CROWDSEC}" + run -0 echo "${stderr}" + assert_output --partial "api server init: plugins are enabled, but the plugin_config section is missing in the configuration" +} + +@test "${FILE} config.yaml: missing config_paths.notification_dir" { + yq e 'del(.config_paths.notification_dir)' -i "${CONFIG_YAML}" + yq e '.notifications=["http_default"]' -i "${PROFILES_PATH}" + run -1 --separate-stderr timeout 2s "${CROWDSEC}" + run -0 echo "${stderr}" + assert_output --partial "api server init: plugins are enabled, but config_paths.notification_dir is not defined" +} + +@test "${FILE} config.yaml: missing config_paths.plugin_dir" { + yq e 'del(.config_paths.plugin_dir)' -i "${CONFIG_YAML}" + yq e '.notifications=["http_default"]' -i "${PROFILES_PATH}" + run -1 --separate-stderr timeout 2s "${CROWDSEC}" + run -0 echo "${stderr}" + assert_output --partial "api server init: plugins are enabled, but config_paths.plugin_dir is not defined" +} + +@test "${FILE} unable to run local API: while reading plugin config" { + yq e '.config_paths.notification_dir="/this/path/does/not/exist"' -i "${CONFIG_YAML}" + yq e '.notifications=["http_default"]' -i "${PROFILES_PATH}" + run -1 --separate-stderr timeout 2s "${CROWDSEC}" + run -0 echo "${stderr}" + assert_output --partial "api server init: unable to run local API: while loading plugin config: open /this/path/does/not/exist: no such file or directory" +} +