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
This commit is contained in:
mmetc 2022-06-06 15:24:48 +02:00 committed by GitHub
parent df7c51f34e
commit 799cc82bb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 388 additions and 278 deletions

3
.dockerignore Normal file
View file

@ -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

View file

@ -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

View file

@ -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`.

View file

@ -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
#

View file

@ -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()

View file

@ -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 {

View file

@ -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)

View file

@ -1,5 +0,0 @@
// +build linux freebsd netbsd openbsd solaris !windows
package main
const DefaultConfigFile = "/etc/crowdsec/config.yaml"

View file

@ -1,6 +0,0 @@
//go:build windows
// +build windows
package main
const DefaultConfigFile = "C:\\ProgramData\\CrowdSec\\config\\config.yaml"

View file

@ -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()

View file

@ -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")

View file

@ -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 {

View file

@ -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" {

View file

@ -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)
}

View file

@ -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
},

View file

@ -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
},

View file

@ -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)
}

View file

@ -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) {

View file

@ -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")

View file

@ -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)

View file

@ -1,5 +0,0 @@
// +build linux freebsd netbsd openbsd solaris !windows
package main
const DefaultConfigFile = "/etc/crowdsec/config.yaml"

View file

@ -1,3 +0,0 @@
package main
const DefaultConfigFile = "C:\\ProgramData\\CrowdSec\\config\\config.yaml"

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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
}

View file

@ -493,6 +493,7 @@ READLOOP:
}
}
//nolint: structcheck,unused
type MockSourceByDSN struct {
configuration.DataSourceCommonCfg `yaml:",inline"`
Toto string `yaml:"toto"`

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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)}

View file

@ -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)}

View file

@ -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()

View file

@ -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)
}
}
}()

View file

@ -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)

View file

@ -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{

View file

@ -1,9 +0,0 @@
//go:build !windows
package apiserver
import "os"
func cleanFile(path string) {
os.Remove(path)
}

View file

@ -1,7 +0,0 @@
package apiserver
import "os"
func cleanFile(path string) {
os.Remove(path)
}

View file

@ -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
}

View file

@ -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 {

View file

@ -133,7 +133,6 @@ func TestGetters(t *testing.T) {
}
func TestIndexDownload(t *testing.T) {
cfg := test_prepenv()
err := UpdateHubIdx(cfg.Hub)

View file

@ -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")
}

View file

@ -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")
}
}

View file

@ -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" {

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}