Merge branch 'master' into enable_context_in_console
This commit is contained in:
commit
9a0d13c3c6
2
.github/workflows/bats-hub.yml
vendored
2
.github/workflows/bats-hub.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: "Set up Go"
|
- name: "Set up Go"
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21.5"
|
go-version: "1.21.6"
|
||||||
|
|
||||||
- name: "Install bats dependencies"
|
- name: "Install bats dependencies"
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/bats-mysql.yml
vendored
2
.github/workflows/bats-mysql.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: "Build + tests"
|
name: "Build + tests"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
2
.github/workflows/bats-postgres.yml
vendored
2
.github/workflows/bats-postgres.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: "Build + tests"
|
name: "Build + tests"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
2
.github/workflows/bats-sqlite-coverage.yml
vendored
2
.github/workflows/bats-sqlite-coverage.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: "Build + tests"
|
name: "Build + tests"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
2
.github/workflows/ci-windows-build-msi.yml
vendored
2
.github/workflows/ci-windows-build-msi.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
|
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -74,7 +74,7 @@ jobs:
|
||||||
- name: "Set up Go"
|
- name: "Set up Go"
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21.0"
|
go-version: "1.21.6"
|
||||||
cache-dependency-path: "**/go.sum"
|
cache-dependency-path: "**/go.sum"
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
|
|
2
.github/workflows/go-tests-windows.yml
vendored
2
.github/workflows/go-tests-windows.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: "Build + tests"
|
name: "Build + tests"
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
2
.github/workflows/go-tests.yml
vendored
2
.github/workflows/go-tests.yml
vendored
|
@ -126,7 +126,7 @@ jobs:
|
||||||
- name: "Set up Go"
|
- name: "Set up Go"
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21.5"
|
go-version: "1.21.6"
|
||||||
|
|
||||||
- name: Create localstack streams
|
- name: Create localstack streams
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -19,6 +19,7 @@ jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Debian Docker image to Docker Hub
|
name: Push Debian Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository_owner == 'crowdsecurity' }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
|
|
|
@ -19,6 +19,7 @@ jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository_owner == 'crowdsecurity' }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.21.5"]
|
go-version: ["1.21.6"]
|
||||||
|
|
||||||
name: Build and upload binary package
|
name: Build and upload binary package
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -9,6 +9,13 @@ run:
|
||||||
- pkg/yamlpatch/merge_test.go
|
- pkg/yamlpatch/merge_test.go
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/crowdsecurity)
|
||||||
|
- prefix(github.com/crowdsecurity/crowdsec)
|
||||||
|
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 30
|
min-complexity: 30
|
||||||
|
|
||||||
|
@ -235,42 +242,6 @@ issues:
|
||||||
|
|
||||||
# Will fix, trivial - just beware of merge conflicts
|
# Will fix, trivial - just beware of merge conflicts
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "expected-actual: need to reverse actual and expected values"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "bool-compare: use assert.False"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "len: use assert.Len"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "bool-compare: use assert.True"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "bool-compare: use require.True"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "require-error: for error assertions use require"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "error-nil: use assert.NoError"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "error-nil: use assert.Error"
|
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "empty: use assert.Empty"
|
|
||||||
|
|
||||||
- linters:
|
- linters:
|
||||||
- perfsprint
|
- perfsprint
|
||||||
text: "fmt.Sprintf can be replaced .*"
|
text: "fmt.Sprintf can be replaced .*"
|
||||||
|
@ -279,10 +250,6 @@ issues:
|
||||||
# Will fix, easy but some neurons required
|
# Will fix, easy but some neurons required
|
||||||
#
|
#
|
||||||
|
|
||||||
- linters:
|
|
||||||
- testifylint
|
|
||||||
text: "float-compare: use assert.InEpsilon .*or InDelta.*"
|
|
||||||
|
|
||||||
- linters:
|
- linters:
|
||||||
- errorlint
|
- errorlint
|
||||||
text: "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors"
|
text: "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# vim: set ft=dockerfile:
|
# vim: set ft=dockerfile:
|
||||||
ARG GOVERSION=1.21.5
|
ARG GOVERSION=1.21.6
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
FROM golang:${GOVERSION}-alpine3.18 AS build
|
FROM golang:${GOVERSION}-alpine3.18 AS build
|
||||||
|
|
||||||
|
@ -7,6 +8,7 @@ WORKDIR /go/src/crowdsec
|
||||||
|
|
||||||
# We like to choose the release of re2 to use, and Alpine does not ship a static version anyway.
|
# We like to choose the release of re2 to use, and Alpine does not ship a static version anyway.
|
||||||
ENV RE2_VERSION=2023-03-01
|
ENV RE2_VERSION=2023-03-01
|
||||||
|
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||||
|
|
||||||
# wizard.sh requires GNU coreutils
|
# wizard.sh requires GNU coreutils
|
||||||
RUN apk add --no-cache git g++ gcc libc-dev make bash gettext binutils-gold coreutils pkgconfig && \
|
RUN apk add --no-cache git g++ gcc libc-dev make bash gettext binutils-gold coreutils pkgconfig && \
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# vim: set ft=dockerfile:
|
# vim: set ft=dockerfile:
|
||||||
ARG GOVERSION=1.21.5
|
ARG GOVERSION=1.21.6
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
FROM golang:${GOVERSION}-bookworm AS build
|
FROM golang:${GOVERSION}-bookworm AS build
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ ENV DEBCONF_NOWARNINGS="yes"
|
||||||
|
|
||||||
# We like to choose the release of re2 to use, the debian version is usually older.
|
# We like to choose the release of re2 to use, the debian version is usually older.
|
||||||
ENV RE2_VERSION=2023-03-01
|
ENV RE2_VERSION=2023-03-01
|
||||||
|
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||||
|
|
||||||
# wizard.sh requires GNU coreutils
|
# wizard.sh requires GNU coreutils
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
|
|
@ -25,9 +25,9 @@ stages:
|
||||||
custom: 'tool'
|
custom: 'tool'
|
||||||
arguments: 'install --global SignClient --version 1.3.155'
|
arguments: 'install --global SignClient --version 1.3.155'
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.20"
|
displayName: "Install Go"
|
||||||
inputs:
|
inputs:
|
||||||
version: '1.21.5'
|
version: '1.21.6'
|
||||||
|
|
||||||
- pwsh: |
|
- pwsh: |
|
||||||
choco install -y make
|
choco install -y make
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (cli cliCapi) NewCommand() *cobra.Command {
|
||||||
Short: "Manage interaction with Central API (CAPI)",
|
Short: "Manage interaction with Central API (CAPI)",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
||||||
if err := require.LAPI(csConfig); err != nil {
|
if err := require.LAPI(csConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,17 @@ func (cli cliCapi) NewCommand() *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli cliCapi) NewRegisterCmd() *cobra.Command {
|
func (cli cliCapi) NewRegisterCmd() *cobra.Command {
|
||||||
var capiUserPrefix string
|
var (
|
||||||
var outputFile string
|
capiUserPrefix string
|
||||||
|
outputFile string
|
||||||
|
)
|
||||||
|
|
||||||
var cmd = &cobra.Command{
|
var cmd = &cobra.Command{
|
||||||
Use: "register",
|
Use: "register",
|
||||||
Short: "Register to Central API (CAPI)",
|
Short: "Register to Central API (CAPI)",
|
||||||
Args: cobra.MinimumNArgs(0),
|
Args: cobra.MinimumNArgs(0),
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
var err error
|
var err error
|
||||||
capiUser, err := generateID(capiUserPrefix)
|
capiUser, err := generateID(capiUserPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,7 +117,7 @@ func (cli cliCapi) NewRegisterCmd() *cobra.Command {
|
||||||
}
|
}
|
||||||
log.Printf("Central API credentials written to '%s'", dumpFile)
|
log.Printf("Central API credentials written to '%s'", dumpFile)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s\n", string(apiConfigDump))
|
fmt.Println(string(apiConfigDump))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warning(ReloadMessage())
|
log.Warning(ReloadMessage())
|
||||||
|
@ -126,6 +128,7 @@ func (cli cliCapi) NewRegisterCmd() *cobra.Command {
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination")
|
cmd.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination")
|
||||||
cmd.Flags().StringVar(&capiUserPrefix, "schmilblick", "", "set a schmilblick (use in tests only)")
|
cmd.Flags().StringVar(&capiUserPrefix, "schmilblick", "", "set a schmilblick (use in tests only)")
|
||||||
|
|
||||||
if err := cmd.Flags().MarkHidden("schmilblick"); err != nil {
|
if err := cmd.Flags().MarkHidden("schmilblick"); err != nil {
|
||||||
log.Fatalf("failed to hide flag: %s", err)
|
log.Fatalf("failed to hide flag: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -134,18 +137,14 @@ func (cli cliCapi) NewRegisterCmd() *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli cliCapi) NewStatusCmd() *cobra.Command {
|
func (cli cliCapi) NewStatusCmd() *cobra.Command {
|
||||||
var cmd = &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "status",
|
Use: "status",
|
||||||
Short: "Check status with the Central API (CAPI)",
|
Short: "Check status with the Central API (CAPI)",
|
||||||
Args: cobra.MinimumNArgs(0),
|
Args: cobra.MinimumNArgs(0),
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
if csConfig.API.Server.OnlineClient == nil {
|
if err := require.CAPIRegistered(csConfig); err != nil {
|
||||||
return fmt.Errorf("please provide credentials for the Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
if csConfig.API.Server.OnlineClient.Credentials == nil {
|
|
||||||
return fmt.Errorf("no credentials for Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password)
|
password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/dumps"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/hubtest"
|
"github.com/crowdsecurity/crowdsec/pkg/hubtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ func GetLineCountForFile(filepath string) (int, error) {
|
||||||
return lc, nil
|
return lc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type cliExplain struct {}
|
type cliExplain struct{}
|
||||||
|
|
||||||
func NewCLIExplain() *cliExplain {
|
func NewCLIExplain() *cliExplain {
|
||||||
return &cliExplain{}
|
return &cliExplain{}
|
||||||
|
@ -109,6 +110,7 @@ tail -n 5 myfile.log | cscli explain --type nginx -f -
|
||||||
flags.Bool("failures", false, "Only show failed lines")
|
flags.Bool("failures", false, "Only show failed lines")
|
||||||
flags.Bool("only-successful-parsers", false, "Only show successful parsers")
|
flags.Bool("only-successful-parsers", false, "Only show successful parsers")
|
||||||
flags.String("crowdsec", "crowdsec", "Path to crowdsec")
|
flags.String("crowdsec", "crowdsec", "Path to crowdsec")
|
||||||
|
flags.Bool("no-clean", false, "Don't clean runtime environment after tests")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -136,13 +138,18 @@ func (cli cliExplain) run(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := hubtest.DumpOpts{}
|
opts := dumps.DumpOpts{}
|
||||||
|
|
||||||
opts.Details, err = flags.GetBool("verbose")
|
opts.Details, err = flags.GetBool("verbose")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
no_clean, err := flags.GetBool("no-clean")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
opts.SkipOk, err = flags.GetBool("failures")
|
opts.SkipOk, err = flags.GetBool("failures")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -172,6 +179,9 @@ func (cli cliExplain) run(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err)
|
return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if no_clean {
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
log.Errorf("unable to delete temporary directory '%s': %s", dir, err)
|
log.Errorf("unable to delete temporary directory '%s': %s", dir, err)
|
||||||
|
@ -254,17 +264,17 @@ func (cli cliExplain) run(cmd *cobra.Command, args []string) error {
|
||||||
parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
|
parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
|
||||||
bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName)
|
bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName)
|
||||||
|
|
||||||
parserDump, err := hubtest.LoadParserDump(parserDumpFile)
|
parserDump, err := dumps.LoadParserDump(parserDumpFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to load parser dump result: %s", err)
|
return fmt.Errorf("unable to load parser dump result: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketStateDump, err := hubtest.LoadBucketPourDump(bucketStateDumpFile)
|
bucketStateDump, err := dumps.LoadBucketPourDump(bucketStateDumpFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to load bucket dump result: %s", err)
|
return fmt.Errorf("unable to load bucket dump result: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hubtest.DumpTree(*parserDump, *bucketStateDump, opts)
|
dumps.DumpTree(*parserDump, *bucketStateDump, opts)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@ func (cli cliHub) upgrade(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if didUpdate {
|
if didUpdate {
|
||||||
updated++
|
updated++
|
||||||
}
|
}
|
||||||
|
@ -191,18 +192,21 @@ func (cli cliHub) types(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print(string(s))
|
fmt.Print(string(s))
|
||||||
case "json":
|
case "json":
|
||||||
jsonStr, err := json.Marshal(cwhub.ItemTypes)
|
jsonStr, err := json.Marshal(cwhub.ItemTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonStr))
|
fmt.Println(string(jsonStr))
|
||||||
case "raw":
|
case "raw":
|
||||||
for _, itemType := range cwhub.ItemTypes {
|
for _, itemType := range cwhub.ItemTypes {
|
||||||
fmt.Println(itemType)
|
fmt.Println(itemType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,11 @@ cscli appsec-configs list crowdsecurity/vpatch`,
|
||||||
|
|
||||||
func NewCLIAppsecRule() *cliItem {
|
func NewCLIAppsecRule() *cliItem {
|
||||||
inspectDetail := func(item *cwhub.Item) error {
|
inspectDetail := func(item *cwhub.Item) error {
|
||||||
//Only show the converted rules in human mode
|
// Only show the converted rules in human mode
|
||||||
if csConfig.Cscli.Output != "human" {
|
if csConfig.Cscli.Output != "human" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
appsecRule := appsec.AppsecCollectionConfig{}
|
appsecRule := appsec.AppsecCollectionConfig{}
|
||||||
|
|
||||||
yamlContent, err := os.ReadFile(item.State.LocalPath)
|
yamlContent, err := os.ReadFile(item.State.LocalPath)
|
||||||
|
@ -71,8 +72,16 @@ func NewCLIAppsecRule() *cliItem {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to convert rule %s : %s", rule.Name, err)
|
return fmt.Errorf("unable to convert rule %s : %s", rule.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(convertedRule)
|
fmt.Println(convertedRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch ruleType { //nolint:gocritic
|
||||||
|
case appsec_rule.ModsecurityRuleType:
|
||||||
|
for _, rule := range appsecRule.SecLangRules {
|
||||||
|
fmt.Println(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/dumps"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/hubtest"
|
"github.com/crowdsecurity/crowdsec/pkg/hubtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -679,8 +680,8 @@ func (cli cliHubTest) NewExplainCmd() *cobra.Command {
|
||||||
return fmt.Errorf("unable to load scenario result after run: %s", err)
|
return fmt.Errorf("unable to load scenario result after run: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opts := hubtest.DumpOpts{}
|
opts := dumps.DumpOpts{}
|
||||||
hubtest.DumpTree(*test.ParserAssert.TestData, *test.ScenarioAssert.PourData, opts)
|
dumps.DumpTree(*test.ParserAssert.TestData, *test.ScenarioAssert.PourData, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -2,11 +2,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/agext/levenshtein"
|
"github.com/agext/levenshtein"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
|
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,12 +2,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
cc "github.com/ivanpirog/coloredcobra"
|
cc "github.com/ivanpirog/coloredcobra"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
|
@ -107,7 +108,7 @@ var NoNeedConfig = []string{
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// set the formatter asap and worry about level later
|
// set the formatter asap and worry about level later
|
||||||
logFormatter := &log.TextFormatter{TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true}
|
logFormatter := &log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true}
|
||||||
log.SetFormatter(logFormatter)
|
log.SetFormatter(logFormatter)
|
||||||
|
|
||||||
if err := fflag.RegisterAllFeatures(); err != nil {
|
if err := fflag.RegisterAllFeatures(); err != nil {
|
||||||
|
|
43
cmd/crowdsec/hook.go
Normal file
43
cmd/crowdsec/hook.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConditionalHook struct {
|
||||||
|
Writer io.Writer
|
||||||
|
LogLevels []log.Level
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ConditionalHook) Fire(entry *log.Entry) error {
|
||||||
|
if hook.Enabled {
|
||||||
|
line, err := entry.String()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = hook.Writer.Write([]byte(line))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ConditionalHook) Levels() []log.Level {
|
||||||
|
return hook.LogLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
// The primal logging hook is set up before parsing config.yaml.
|
||||||
|
// Once config.yaml is parsed, the primal hook is disabled if the
|
||||||
|
// configured logger is writing to stderr. Otherwise it's used to
|
||||||
|
// report fatal errors and panics to stderr in addition to the log file.
|
||||||
|
var primalHook = &ConditionalHook{
|
||||||
|
Writer: os.Stderr,
|
||||||
|
LogLevels: []log.Level{log.FatalLevel, log.PanicLevel},
|
||||||
|
Enabled: true,
|
||||||
|
}
|
|
@ -80,11 +80,13 @@ func LoadBuckets(cConfig *csconfig.Config, hub *cwhub.Hub) error {
|
||||||
err error
|
err error
|
||||||
files []string
|
files []string
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, hubScenarioItem := range hub.GetItemMap(cwhub.SCENARIOS) {
|
for _, hubScenarioItem := range hub.GetItemMap(cwhub.SCENARIOS) {
|
||||||
if hubScenarioItem.State.Installed {
|
if hubScenarioItem.State.Installed {
|
||||||
files = append(files, hubScenarioItem.State.LocalPath)
|
files = append(files, hubScenarioItem.State.LocalPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets = leakybucket.NewBuckets()
|
buckets = leakybucket.NewBuckets()
|
||||||
|
|
||||||
log.Infof("Loading %d scenario files", len(files))
|
log.Infof("Loading %d scenario files", len(files))
|
||||||
|
@ -99,6 +101,7 @@ func LoadBuckets(cConfig *csconfig.Config, hub *cwhub.Hub) error {
|
||||||
holders[holderIndex].Profiling = true
|
holders[holderIndex].Profiling = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +146,10 @@ func (l labelsMap) Set(label string) error {
|
||||||
if len(split) != 2 {
|
if len(split) != 2 {
|
||||||
return fmt.Errorf("invalid format for label '%s', must be key:value", pair)
|
return fmt.Errorf("invalid format for label '%s', must be key:value", pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
l[split[0]] = split[1]
|
l[split[0]] = split[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,9 +173,11 @@ func (f *Flags) Parse() {
|
||||||
flag.BoolVar(&f.DisableAPI, "no-api", false, "disable local API")
|
flag.BoolVar(&f.DisableAPI, "no-api", false, "disable local API")
|
||||||
flag.BoolVar(&f.DisableCAPI, "no-capi", false, "disable communication with Central API")
|
flag.BoolVar(&f.DisableCAPI, "no-capi", false, "disable communication with Central API")
|
||||||
flag.BoolVar(&f.OrderEvent, "order-event", false, "enforce event ordering with significant performance cost")
|
flag.BoolVar(&f.OrderEvent, "order-event", false, "enforce event ordering with significant performance cost")
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
flag.StringVar(&f.WinSvc, "winsvc", "", "Windows service Action: Install, Remove etc..")
|
flag.StringVar(&f.WinSvc, "winsvc", "", "Windows service Action: Install, Remove etc..")
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.StringVar(&dumpFolder, "dump-data", "", "dump parsers/buckets raw outputs")
|
flag.StringVar(&dumpFolder, "dump-data", "", "dump parsers/buckets raw outputs")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
@ -205,6 +212,7 @@ func newLogLevel(curLevelPtr *log.Level, f *Flags) *log.Level {
|
||||||
// avoid returning a new ptr to the same value
|
// avoid returning a new ptr to the same value
|
||||||
return curLevelPtr
|
return curLevelPtr
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +246,8 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
primalHook.Enabled = (cConfig.Common.LogMedia != "stdout")
|
||||||
|
|
||||||
if err := csconfig.LoadFeatureFlagsFile(configFile, log.StandardLogger()); err != nil {
|
if err := csconfig.LoadFeatureFlagsFile(configFile, log.StandardLogger()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -282,6 +292,7 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
|
||||||
if cConfig.DisableAPI {
|
if cConfig.DisableAPI {
|
||||||
cConfig.Common.Daemonize = false
|
cConfig.Common.Daemonize = false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("single file mode : log_media=%s daemonize=%t", cConfig.Common.LogMedia, cConfig.Common.Daemonize)
|
log.Infof("single file mode : log_media=%s daemonize=%t", cConfig.Common.LogMedia, cConfig.Common.Daemonize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +302,7 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
|
||||||
|
|
||||||
if cConfig.Common.Daemonize && runtime.GOOS == "windows" {
|
if cConfig.Common.Daemonize && runtime.GOOS == "windows" {
|
||||||
log.Debug("Daemonization is not supported on Windows, disabling")
|
log.Debug("Daemonization is not supported on Windows, disabling")
|
||||||
|
|
||||||
cConfig.Common.Daemonize = false
|
cConfig.Common.Daemonize = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +320,8 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
|
||||||
var crowdsecT0 time.Time
|
var crowdsecT0 time.Time
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.AddHook(primalHook)
|
||||||
|
|
||||||
if err := fflag.RegisterAllFeatures(); err != nil {
|
if err := fflag.RegisterAllFeatures(); err != nil {
|
||||||
log.Fatalf("failed to register features: %s", err)
|
log.Fatalf("failed to register features: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -342,5 +356,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,15 @@ func runOutput(input chan types.Event, overflow chan types.Event, buckets *leaky
|
||||||
return fmt.Errorf("loading list of installed hub scenarios: %w", err)
|
return fmt.Errorf("loading list of installed hub scenarios: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appsecRules, err := hub.GetInstalledItemNames(cwhub.APPSEC_RULES)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading list of installed hub appsec rules: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
installedScenariosAndAppsecRules := make([]string, 0, len(scenarios)+len(appsecRules))
|
||||||
|
installedScenariosAndAppsecRules = append(installedScenariosAndAppsecRules, scenarios...)
|
||||||
|
installedScenariosAndAppsecRules = append(installedScenariosAndAppsecRules, appsecRules...)
|
||||||
|
|
||||||
apiURL, err := url.Parse(apiConfig.URL)
|
apiURL, err := url.Parse(apiConfig.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing api url ('%s'): %w", apiConfig.URL, err)
|
return fmt.Errorf("parsing api url ('%s'): %w", apiConfig.URL, err)
|
||||||
|
@ -87,14 +96,27 @@ func runOutput(input chan types.Event, overflow chan types.Event, buckets *leaky
|
||||||
password := strfmt.Password(apiConfig.Password)
|
password := strfmt.Password(apiConfig.Password)
|
||||||
|
|
||||||
Client, err := apiclient.NewClient(&apiclient.Config{
|
Client, err := apiclient.NewClient(&apiclient.Config{
|
||||||
MachineID: apiConfig.Login,
|
MachineID: apiConfig.Login,
|
||||||
Password: password,
|
Password: password,
|
||||||
Scenarios: scenarios,
|
Scenarios: installedScenariosAndAppsecRules,
|
||||||
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
PapiURL: papiURL,
|
PapiURL: papiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
UpdateScenario: func() ([]string, error) {return hub.GetInstalledItemNames(cwhub.SCENARIOS)},
|
UpdateScenario: func() ([]string, error) {
|
||||||
|
scenarios, err := hub.GetInstalledItemNames(cwhub.SCENARIOS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
appsecRules, err := hub.GetInstalledItemNames(cwhub.APPSEC_RULES)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret := make([]string, 0, len(scenarios)+len(appsecRules))
|
||||||
|
ret = append(ret, scenarios...)
|
||||||
|
ret = append(ret, appsecRules...)
|
||||||
|
return ret, nil
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("new client api: %w", err)
|
return fmt.Errorf("new client api: %w", err)
|
||||||
|
@ -102,7 +124,7 @@ func runOutput(input chan types.Event, overflow chan types.Event, buckets *leaky
|
||||||
authResp, _, err := Client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
authResp, _, err := Client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
||||||
MachineID: &apiConfig.Login,
|
MachineID: &apiConfig.Login,
|
||||||
Password: &password,
|
Password: &password,
|
||||||
Scenarios: scenarios,
|
Scenarios: installedScenariosAndAppsecRules,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("authenticate watcher (%s): %w", apiConfig.Login, err)
|
return fmt.Errorf("authenticate watcher (%s): %w", apiConfig.Login, err)
|
||||||
|
|
|
@ -4,10 +4,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/sirupsen/logrus/hooks/writer"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/trace"
|
"github.com/crowdsecurity/go-cs-lib/trace"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
|
@ -24,16 +22,6 @@ func StartRunSvc() error {
|
||||||
|
|
||||||
defer trace.CatchPanic("crowdsec/StartRunSvc")
|
defer trace.CatchPanic("crowdsec/StartRunSvc")
|
||||||
|
|
||||||
// Set a default logger with level=fatal on stderr,
|
|
||||||
// in addition to the one we configure afterwards
|
|
||||||
log.AddHook(&writer.Hook{
|
|
||||||
Writer: os.Stderr,
|
|
||||||
LogLevels: []log.Level{
|
|
||||||
log.PanicLevel,
|
|
||||||
log.FatalLevel,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false); err != nil {
|
if cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -46,6 +34,7 @@ func StartRunSvc() error {
|
||||||
// Enable profiling early
|
// Enable profiling early
|
||||||
if cConfig.Prometheus != nil {
|
if cConfig.Prometheus != nil {
|
||||||
var dbClient *database.Client
|
var dbClient *database.Client
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if cConfig.DbConfig != nil {
|
if cConfig.DbConfig != nil {
|
||||||
|
@ -55,8 +44,11 @@ func StartRunSvc() error {
|
||||||
return fmt.Errorf("unable to create database client: %s", err)
|
return fmt.Errorf("unable to create database client: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPrometheus(cConfig.Prometheus)
|
registerPrometheus(cConfig.Prometheus)
|
||||||
|
|
||||||
go servePrometheus(cConfig.Prometheus, dbClient, apiReady, agentReady)
|
go servePrometheus(cConfig.Prometheus, dbClient, apiReady, agentReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Serve(cConfig, apiReady, agentReady)
|
return Serve(cConfig, apiReady, agentReady)
|
||||||
}
|
}
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -24,9 +24,9 @@ require (
|
||||||
github.com/buger/jsonparser v1.1.1
|
github.com/buger/jsonparser v1.1.1
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/cespare/xxhash/v2 v2.2.0
|
github.com/cespare/xxhash/v2 v2.2.0
|
||||||
github.com/crowdsecurity/coraza/v3 v3.0.0-20231213144607-41d5358da94f
|
github.com/crowdsecurity/coraza/v3 v3.0.0-20240108124027-a62b8d8e5607
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
|
||||||
github.com/crowdsecurity/go-cs-lib v0.0.5
|
github.com/crowdsecurity/go-cs-lib v0.0.6
|
||||||
github.com/crowdsecurity/grokky v0.2.1
|
github.com/crowdsecurity/grokky v0.2.1
|
||||||
github.com/crowdsecurity/machineid v1.0.2
|
github.com/crowdsecurity/machineid v1.0.2
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
@ -201,7 +201,7 @@ require (
|
||||||
go.mongodb.org/mongo-driver v1.9.4 // indirect
|
go.mongodb.org/mongo-driver v1.9.4 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/term v0.15.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 // indirect
|
golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -100,10 +100,14 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/crowdsecurity/coraza/v3 v3.0.0-20231213144607-41d5358da94f h1:FkOB9aDw0xzDd14pTarGRLsUNAymONq3dc7zhvsXElg=
|
github.com/crowdsecurity/coraza/v3 v3.0.0-20231213144607-41d5358da94f h1:FkOB9aDw0xzDd14pTarGRLsUNAymONq3dc7zhvsXElg=
|
||||||
github.com/crowdsecurity/coraza/v3 v3.0.0-20231213144607-41d5358da94f/go.mod h1:TrU7Li+z2RHNrPy0TKJ6R65V6Yzpan2sTIRryJJyJso=
|
github.com/crowdsecurity/coraza/v3 v3.0.0-20231213144607-41d5358da94f/go.mod h1:TrU7Li+z2RHNrPy0TKJ6R65V6Yzpan2sTIRryJJyJso=
|
||||||
|
github.com/crowdsecurity/coraza/v3 v3.0.0-20240108124027-a62b8d8e5607 h1:hyrYw3h8clMcRL2u5ooZ3tmwnmJftmhb9Ws1MKmavvI=
|
||||||
|
github.com/crowdsecurity/coraza/v3 v3.0.0-20240108124027-a62b8d8e5607/go.mod h1:br36fEqurGYZQGit+iDYsIzW0FF6VufMbDzyyLxEuPA=
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
|
||||||
github.com/crowdsecurity/go-cs-lib v0.0.5 h1:eVLW+BRj3ZYn0xt5/xmgzfbbB8EBo32gM4+WpQQk2e8=
|
github.com/crowdsecurity/go-cs-lib v0.0.5 h1:eVLW+BRj3ZYn0xt5/xmgzfbbB8EBo32gM4+WpQQk2e8=
|
||||||
github.com/crowdsecurity/go-cs-lib v0.0.5/go.mod h1:8FMKNGsh3hMZi2SEv6P15PURhEJnZV431XjzzBSuf0k=
|
github.com/crowdsecurity/go-cs-lib v0.0.5/go.mod h1:8FMKNGsh3hMZi2SEv6P15PURhEJnZV431XjzzBSuf0k=
|
||||||
|
github.com/crowdsecurity/go-cs-lib v0.0.6 h1:Ef6MylXe0GaJE9vrfvxEdbHb31+JUP1os+murPz7Pos=
|
||||||
|
github.com/crowdsecurity/go-cs-lib v0.0.6/go.mod h1:8FMKNGsh3hMZi2SEv6P15PURhEJnZV431XjzzBSuf0k=
|
||||||
github.com/crowdsecurity/grokky v0.2.1 h1:t4VYnDlAd0RjDM2SlILalbwfCrQxtJSMGdQOR0zwkE4=
|
github.com/crowdsecurity/grokky v0.2.1 h1:t4VYnDlAd0RjDM2SlILalbwfCrQxtJSMGdQOR0zwkE4=
|
||||||
github.com/crowdsecurity/grokky v0.2.1/go.mod h1:33usDIYzGDsgX1kHAThCbseso6JuWNJXOzRQDGXHtWM=
|
github.com/crowdsecurity/grokky v0.2.1/go.mod h1:33usDIYzGDsgX1kHAThCbseso6JuWNJXOzRQDGXHtWM=
|
||||||
github.com/crowdsecurity/machineid v1.0.2 h1:wpkpsUghJF8Khtmn/tg6GxgdhLA1Xflerh5lirI+bdc=
|
github.com/crowdsecurity/machineid v1.0.2 h1:wpkpsUghJF8Khtmn/tg6GxgdhLA1Xflerh5lirI+bdc=
|
||||||
|
@ -807,6 +811,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
BUILD_GOVERSION = $(subst go,,$(shell go env GOVERSION))
|
BUILD_GOVERSION = $(subst go,,$(shell $(GO) env GOVERSION))
|
||||||
|
|
||||||
go_major_minor = $(subst ., ,$(BUILD_GOVERSION))
|
go_major_minor = $(subst ., ,$(BUILD_GOVERSION))
|
||||||
GO_MAJOR_VERSION = $(word 1, $(go_major_minor))
|
GO_MAJOR_VERSION = $(word 1, $(go_major_minor))
|
||||||
|
@ -9,7 +9,7 @@ GO_VERSION_VALIDATION_ERR_MSG = Golang version ($(BUILD_GOVERSION)) is not suppo
|
||||||
|
|
||||||
|
|
||||||
.PHONY: goversion
|
.PHONY: goversion
|
||||||
goversion: $(if $(findstring devel,$(shell go env GOVERSION)),goversion_devel,goversion_check)
|
goversion: $(if $(findstring devel,$(shell $(GO) env GOVERSION)),goversion_devel,goversion_check)
|
||||||
|
|
||||||
|
|
||||||
.PHONY: goversion_devel
|
.PHONY: goversion_devel
|
||||||
|
|
|
@ -8,7 +8,11 @@ MKDIR=mkdir -p
|
||||||
GOOS ?= $(shell go env GOOS)
|
GOOS ?= $(shell go env GOOS)
|
||||||
|
|
||||||
# Current versioning information from env
|
# Current versioning information from env
|
||||||
BUILD_VERSION?=$(shell git describe --tags)
|
# The $(or) is used to ignore an empty BUILD_VERSION when it's an envvar,
|
||||||
|
# like inside a docker build: docker build --build-arg BUILD_VERSION=1.2.3
|
||||||
|
# as opposed to a make parameter: make BUILD_VERSION=1.2.3
|
||||||
|
BUILD_VERSION:=$(or $(BUILD_VERSION),$(shell git describe --tags --dirty))
|
||||||
|
|
||||||
BUILD_TIMESTAMP=$(shell date +%F"_"%T)
|
BUILD_TIMESTAMP=$(shell date +%F"_"%T)
|
||||||
DEFAULT_CONFIGDIR?=/etc/crowdsec
|
DEFAULT_CONFIGDIR?=/etc/crowdsec
|
||||||
DEFAULT_DATADIR?=/var/lib/crowdsec/data
|
DEFAULT_DATADIR?=/var/lib/crowdsec/data
|
||||||
|
|
|
@ -40,15 +40,19 @@ func (f *MockSource) Configure(cfg []byte, logger *log.Entry) error {
|
||||||
if err := f.UnmarshalConfig(cfg); err != nil {
|
if err := f.UnmarshalConfig(cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Mode == "" {
|
if f.Mode == "" {
|
||||||
f.Mode = configuration.CAT_MODE
|
f.Mode = configuration.CAT_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Mode != configuration.CAT_MODE && f.Mode != configuration.TAIL_MODE {
|
if f.Mode != configuration.CAT_MODE && f.Mode != configuration.TAIL_MODE {
|
||||||
return fmt.Errorf("mode %s is not supported", f.Mode)
|
return fmt.Errorf("mode %s is not supported", f.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Toto == "" {
|
if f.Toto == "" {
|
||||||
return fmt.Errorf("expect non-empty toto")
|
return fmt.Errorf("expect non-empty toto")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *MockSource) GetMode() string { return f.Mode }
|
func (f *MockSource) GetMode() string { return f.Mode }
|
||||||
|
@ -77,6 +81,7 @@ func appendMockSource() {
|
||||||
if GetDataSourceIface("mock") == nil {
|
if GetDataSourceIface("mock") == nil {
|
||||||
AcquisitionSources["mock"] = func() DataSource { return &MockSource{} }
|
AcquisitionSources["mock"] = func() DataSource { return &MockSource{} }
|
||||||
}
|
}
|
||||||
|
|
||||||
if GetDataSourceIface("mock_cant_run") == nil {
|
if GetDataSourceIface("mock_cant_run") == nil {
|
||||||
AcquisitionSources["mock_cant_run"] = func() DataSource { return &MockSourceCantRun{} }
|
AcquisitionSources["mock_cant_run"] = func() DataSource { return &MockSourceCantRun{} }
|
||||||
}
|
}
|
||||||
|
@ -84,6 +89,7 @@ func appendMockSource() {
|
||||||
|
|
||||||
func TestDataSourceConfigure(t *testing.T) {
|
func TestDataSourceConfigure(t *testing.T) {
|
||||||
appendMockSource()
|
appendMockSource()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
TestName string
|
TestName string
|
||||||
String string
|
String string
|
||||||
|
@ -185,22 +191,22 @@ wowo: ajsajasjas
|
||||||
switch tc.TestName {
|
switch tc.TestName {
|
||||||
case "basic_valid_config":
|
case "basic_valid_config":
|
||||||
mock := (*ds).Dump().(*MockSource)
|
mock := (*ds).Dump().(*MockSource)
|
||||||
assert.Equal(t, mock.Toto, "test_value1")
|
assert.Equal(t, "test_value1", mock.Toto)
|
||||||
assert.Equal(t, mock.Mode, "cat")
|
assert.Equal(t, "cat", mock.Mode)
|
||||||
assert.Equal(t, mock.logger.Logger.Level, log.InfoLevel)
|
assert.Equal(t, log.InfoLevel, mock.logger.Logger.Level)
|
||||||
assert.Equal(t, mock.Labels, map[string]string{"test": "foobar"})
|
assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels)
|
||||||
case "basic_debug_config":
|
case "basic_debug_config":
|
||||||
mock := (*ds).Dump().(*MockSource)
|
mock := (*ds).Dump().(*MockSource)
|
||||||
assert.Equal(t, mock.Toto, "test_value1")
|
assert.Equal(t, "test_value1", mock.Toto)
|
||||||
assert.Equal(t, mock.Mode, "cat")
|
assert.Equal(t, "cat", mock.Mode)
|
||||||
assert.Equal(t, mock.logger.Logger.Level, log.DebugLevel)
|
assert.Equal(t, log.DebugLevel, mock.logger.Logger.Level)
|
||||||
assert.Equal(t, mock.Labels, map[string]string{"test": "foobar"})
|
assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels)
|
||||||
case "basic_tailmode_config":
|
case "basic_tailmode_config":
|
||||||
mock := (*ds).Dump().(*MockSource)
|
mock := (*ds).Dump().(*MockSource)
|
||||||
assert.Equal(t, mock.Toto, "test_value1")
|
assert.Equal(t, "test_value1", mock.Toto)
|
||||||
assert.Equal(t, mock.Mode, "tail")
|
assert.Equal(t, "tail", mock.Mode)
|
||||||
assert.Equal(t, mock.logger.Logger.Level, log.DebugLevel)
|
assert.Equal(t, log.DebugLevel, mock.logger.Logger.Level)
|
||||||
assert.Equal(t, mock.Labels, map[string]string{"test": "foobar"})
|
assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -208,6 +214,7 @@ wowo: ajsajasjas
|
||||||
|
|
||||||
func TestLoadAcquisitionFromFile(t *testing.T) {
|
func TestLoadAcquisitionFromFile(t *testing.T) {
|
||||||
appendMockSource()
|
appendMockSource()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
TestName string
|
TestName string
|
||||||
Config csconfig.CrowdsecServiceCfg
|
Config csconfig.CrowdsecServiceCfg
|
||||||
|
@ -284,7 +291,6 @@ func TestLoadAcquisitionFromFile(t *testing.T) {
|
||||||
|
|
||||||
assert.Len(t, dss, tc.ExpectedLen)
|
assert.Len(t, dss, tc.ExpectedLen)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,9 +310,11 @@ func (f *MockCat) Configure(cfg []byte, logger *log.Entry) error {
|
||||||
if f.Mode == "" {
|
if f.Mode == "" {
|
||||||
f.Mode = configuration.CAT_MODE
|
f.Mode = configuration.CAT_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Mode != configuration.CAT_MODE {
|
if f.Mode != configuration.CAT_MODE {
|
||||||
return fmt.Errorf("mode %s is not supported", f.Mode)
|
return fmt.Errorf("mode %s is not supported", f.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +327,7 @@ func (f *MockCat) OneShotAcquisition(out chan types.Event, tomb *tomb.Tomb) erro
|
||||||
evt.Line.Src = "test"
|
evt.Line.Src = "test"
|
||||||
out <- evt
|
out <- evt
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *MockCat) StreamingAcquisition(chan types.Event, *tomb.Tomb) error {
|
func (f *MockCat) StreamingAcquisition(chan types.Event, *tomb.Tomb) error {
|
||||||
|
@ -345,9 +354,11 @@ func (f *MockTail) Configure(cfg []byte, logger *log.Entry) error {
|
||||||
if f.Mode == "" {
|
if f.Mode == "" {
|
||||||
f.Mode = configuration.TAIL_MODE
|
f.Mode = configuration.TAIL_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Mode != configuration.TAIL_MODE {
|
if f.Mode != configuration.TAIL_MODE {
|
||||||
return fmt.Errorf("mode %s is not supported", f.Mode)
|
return fmt.Errorf("mode %s is not supported", f.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,6 +375,7 @@ func (f *MockTail) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) erro
|
||||||
out <- evt
|
out <- evt
|
||||||
}
|
}
|
||||||
<-t.Dying()
|
<-t.Dying()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *MockTail) CanRun() error { return nil }
|
func (f *MockTail) CanRun() error { return nil }
|
||||||
|
@ -446,6 +458,7 @@ func (f *MockTailError) StreamingAcquisition(out chan types.Event, t *tomb.Tomb)
|
||||||
out <- evt
|
out <- evt
|
||||||
}
|
}
|
||||||
t.Kill(fmt.Errorf("got error (tomb)"))
|
t.Kill(fmt.Errorf("got error (tomb)"))
|
||||||
|
|
||||||
return fmt.Errorf("got error")
|
return fmt.Errorf("got error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,6 +512,7 @@ func (f *MockSourceByDSN) ConfigureByDSN(dsn string, labels map[string]string, l
|
||||||
if dsn != "test_expect" {
|
if dsn != "test_expect" {
|
||||||
return fmt.Errorf("unexpected value")
|
return fmt.Errorf("unexpected value")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *MockSourceByDSN) GetUuid() string { return "" }
|
func (f *MockSourceByDSN) GetUuid() string { return "" }
|
||||||
|
|
|
@ -335,7 +335,7 @@ func (w *AppsecSource) appsecHandler(rw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the request only once
|
// parse the request only once
|
||||||
parsedRequest, err := appsec.NewParsedRequestFromRequest(r)
|
parsedRequest, err := appsec.NewParsedRequestFromRequest(r, w.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.logger.Errorf("%s", err)
|
w.logger.Errorf("%s", err)
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
|
|
@ -57,6 +57,7 @@ container_name:
|
||||||
subLogger := log.WithFields(log.Fields{
|
subLogger := log.WithFields(log.Fields{
|
||||||
"type": "docker",
|
"type": "docker",
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
f := DockerSource{}
|
f := DockerSource{}
|
||||||
err := f.Configure([]byte(test.config), subLogger)
|
err := f.Configure([]byte(test.config), subLogger)
|
||||||
|
@ -66,12 +67,15 @@ container_name:
|
||||||
|
|
||||||
func TestConfigureDSN(t *testing.T) {
|
func TestConfigureDSN(t *testing.T) {
|
||||||
log.Infof("Test 'TestConfigureDSN'")
|
log.Infof("Test 'TestConfigureDSN'")
|
||||||
|
|
||||||
var dockerHost string
|
var dockerHost string
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
dockerHost = "npipe:////./pipe/docker_engine"
|
dockerHost = "npipe:////./pipe/docker_engine"
|
||||||
} else {
|
} else {
|
||||||
dockerHost = "unix:///var/run/podman/podman.sock"
|
dockerHost = "unix:///var/run/podman/podman.sock"
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
dsn string
|
dsn string
|
||||||
|
@ -106,6 +110,7 @@ func TestConfigureDSN(t *testing.T) {
|
||||||
subLogger := log.WithFields(log.Fields{
|
subLogger := log.WithFields(log.Fields{
|
||||||
"type": "docker",
|
"type": "docker",
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
f := DockerSource{}
|
f := DockerSource{}
|
||||||
err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "")
|
err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "")
|
||||||
|
@ -156,8 +161,11 @@ container_name_regexp:
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ts := range tests {
|
for _, ts := range tests {
|
||||||
var logger *log.Logger
|
var (
|
||||||
var subLogger *log.Entry
|
logger *log.Logger
|
||||||
|
subLogger *log.Entry
|
||||||
|
)
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
logger.SetLevel(ts.logLevel)
|
logger.SetLevel(ts.logLevel)
|
||||||
subLogger = logger.WithFields(log.Fields{
|
subLogger = logger.WithFields(log.Fields{
|
||||||
|
@ -173,10 +181,12 @@ container_name_regexp:
|
||||||
dockerTomb := tomb.Tomb{}
|
dockerTomb := tomb.Tomb{}
|
||||||
out := make(chan types.Event)
|
out := make(chan types.Event)
|
||||||
dockerSource := DockerSource{}
|
dockerSource := DockerSource{}
|
||||||
|
|
||||||
err := dockerSource.Configure([]byte(ts.config), subLogger)
|
err := dockerSource.Configure([]byte(ts.config), subLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error : %s", err)
|
t.Fatalf("Unexpected error : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dockerSource.Client = new(mockDockerCli)
|
dockerSource.Client = new(mockDockerCli)
|
||||||
actualLines := 0
|
actualLines := 0
|
||||||
readerTomb := &tomb.Tomb{}
|
readerTomb := &tomb.Tomb{}
|
||||||
|
@ -204,21 +214,23 @@ container_name_regexp:
|
||||||
if err := readerTomb.Wait(); err != nil {
|
if err := readerTomb.Wait(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.expectedLines != 0 {
|
if ts.expectedLines != 0 {
|
||||||
assert.Equal(t, ts.expectedLines, actualLines)
|
assert.Equal(t, ts.expectedLines, actualLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = streamTomb.Wait()
|
err = streamTomb.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("docker acquisition error: %s", err)
|
t.Fatalf("docker acquisition error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *mockDockerCli) ContainerList(ctx context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error) {
|
func (cli *mockDockerCli) ContainerList(ctx context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error) {
|
||||||
if readLogs == true {
|
if readLogs == true {
|
||||||
return []dockerTypes.Container{}, nil
|
return []dockerTypes.Container{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
containers := make([]dockerTypes.Container, 0)
|
containers := make([]dockerTypes.Container, 0)
|
||||||
container := &dockerTypes.Container{
|
container := &dockerTypes.Container{
|
||||||
ID: "12456",
|
ID: "12456",
|
||||||
|
@ -233,16 +245,20 @@ func (cli *mockDockerCli) ContainerLogs(ctx context.Context, container string, o
|
||||||
if readLogs == true {
|
if readLogs == true {
|
||||||
return io.NopCloser(strings.NewReader("")), nil
|
return io.NopCloser(strings.NewReader("")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
readLogs = true
|
readLogs = true
|
||||||
data := []string{"docker\n", "test\n", "1234\n"}
|
data := []string{"docker\n", "test\n", "1234\n"}
|
||||||
ret := ""
|
ret := ""
|
||||||
|
|
||||||
for _, line := range data {
|
for _, line := range data {
|
||||||
startLineByte := make([]byte, 8)
|
startLineByte := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint32(startLineByte, 1) //stdout stream
|
binary.LittleEndian.PutUint32(startLineByte, 1) //stdout stream
|
||||||
binary.BigEndian.PutUint32(startLineByte[4:], uint32(len(line)))
|
binary.BigEndian.PutUint32(startLineByte[4:], uint32(len(line)))
|
||||||
ret += fmt.Sprintf("%s%s", startLineByte, line)
|
ret += fmt.Sprintf("%s%s", startLineByte, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := io.NopCloser(strings.NewReader(ret)) // r type is io.ReadCloser
|
r := io.NopCloser(strings.NewReader(ret)) // r type is io.ReadCloser
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +268,7 @@ func (cli *mockDockerCli) ContainerInspect(ctx context.Context, c string) (docke
|
||||||
Tty: false,
|
Tty: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,8 +302,11 @@ func TestOneShot(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ts := range tests {
|
for _, ts := range tests {
|
||||||
var subLogger *log.Entry
|
var (
|
||||||
var logger *log.Logger
|
subLogger *log.Entry
|
||||||
|
logger *log.Logger
|
||||||
|
)
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
logger.SetLevel(ts.logLevel)
|
logger.SetLevel(ts.logLevel)
|
||||||
subLogger = logger.WithFields(log.Fields{
|
subLogger = logger.WithFields(log.Fields{
|
||||||
|
@ -307,6 +327,7 @@ func TestOneShot(t *testing.T) {
|
||||||
if err := dockerClient.ConfigureByDSN(ts.dsn, labels, subLogger, ""); err != nil {
|
if err := dockerClient.ConfigureByDSN(ts.dsn, labels, subLogger, ""); err != nil {
|
||||||
t.Fatalf("unable to configure dsn '%s': %s", ts.dsn, err)
|
t.Fatalf("unable to configure dsn '%s': %s", ts.dsn, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dockerClient.Client = new(mockDockerCli)
|
dockerClient.Client = new(mockDockerCli)
|
||||||
out := make(chan types.Event, 100)
|
out := make(chan types.Event, 100)
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
|
@ -315,8 +336,7 @@ func TestOneShot(t *testing.T) {
|
||||||
|
|
||||||
// else we do the check before actualLines is incremented ...
|
// else we do the check before actualLines is incremented ...
|
||||||
if ts.expectedLines != 0 {
|
if ts.expectedLines != 0 {
|
||||||
assert.Equal(t, ts.expectedLines, len(out))
|
assert.Len(t, out, ts.expectedLines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ func TestBadConfiguration(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("Skipping test on windows")
|
t.Skip("Skipping test on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config string
|
config string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
@ -48,6 +49,7 @@ journalctl_filter:
|
||||||
subLogger := log.WithFields(log.Fields{
|
subLogger := log.WithFields(log.Fields{
|
||||||
"type": "journalctl",
|
"type": "journalctl",
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
f := JournalCtlSource{}
|
f := JournalCtlSource{}
|
||||||
err := f.Configure([]byte(test.config), subLogger)
|
err := f.Configure([]byte(test.config), subLogger)
|
||||||
|
@ -59,6 +61,7 @@ func TestConfigureDSN(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("Skipping test on windows")
|
t.Skip("Skipping test on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
dsn string
|
dsn string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
@ -92,9 +95,11 @@ func TestConfigureDSN(t *testing.T) {
|
||||||
expectedErr: "",
|
expectedErr: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
subLogger := log.WithFields(log.Fields{
|
subLogger := log.WithFields(log.Fields{
|
||||||
"type": "journalctl",
|
"type": "journalctl",
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
f := JournalCtlSource{}
|
f := JournalCtlSource{}
|
||||||
err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "")
|
err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger, "")
|
||||||
|
@ -106,6 +111,7 @@ func TestOneShot(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("Skipping test on windows")
|
t.Skip("Skipping test on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config string
|
config string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
@ -137,9 +143,12 @@ journalctl_filter:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, ts := range tests {
|
for _, ts := range tests {
|
||||||
var logger *log.Logger
|
var (
|
||||||
var subLogger *log.Entry
|
logger *log.Logger
|
||||||
var hook *test.Hook
|
subLogger *log.Entry
|
||||||
|
hook *test.Hook
|
||||||
|
)
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
logger, hook = test.NewNullLogger()
|
logger, hook = test.NewNullLogger()
|
||||||
logger.SetLevel(ts.logLevel)
|
logger.SetLevel(ts.logLevel)
|
||||||
|
@ -151,27 +160,32 @@ journalctl_filter:
|
||||||
"type": "journalctl",
|
"type": "journalctl",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
out := make(chan types.Event, 100)
|
out := make(chan types.Event, 100)
|
||||||
j := JournalCtlSource{}
|
j := JournalCtlSource{}
|
||||||
|
|
||||||
err := j.Configure([]byte(ts.config), subLogger)
|
err := j.Configure([]byte(ts.config), subLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error : %s", err)
|
t.Fatalf("Unexpected error : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = j.OneShotAcquisition(out, &tomb)
|
err = j.OneShotAcquisition(out, &tomb)
|
||||||
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.expectedLines != 0 {
|
if ts.expectedLines != 0 {
|
||||||
assert.Equal(t, ts.expectedLines, len(out))
|
assert.Len(t, out, ts.expectedLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
if hook.LastEntry() == nil {
|
if hook.LastEntry() == nil {
|
||||||
t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
|
t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
|
assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
|
||||||
hook.Reset()
|
hook.Reset()
|
||||||
}
|
}
|
||||||
|
@ -182,6 +196,7 @@ func TestStreaming(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("Skipping test on windows")
|
t.Skip("Skipping test on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config string
|
config string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
@ -202,9 +217,12 @@ journalctl_filter:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, ts := range tests {
|
for _, ts := range tests {
|
||||||
var logger *log.Logger
|
var (
|
||||||
var subLogger *log.Entry
|
logger *log.Logger
|
||||||
var hook *test.Hook
|
subLogger *log.Entry
|
||||||
|
hook *test.Hook
|
||||||
|
)
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
logger, hook = test.NewNullLogger()
|
logger, hook = test.NewNullLogger()
|
||||||
logger.SetLevel(ts.logLevel)
|
logger.SetLevel(ts.logLevel)
|
||||||
|
@ -216,14 +234,18 @@ journalctl_filter:
|
||||||
"type": "journalctl",
|
"type": "journalctl",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
out := make(chan types.Event)
|
out := make(chan types.Event)
|
||||||
j := JournalCtlSource{}
|
j := JournalCtlSource{}
|
||||||
|
|
||||||
err := j.Configure([]byte(ts.config), subLogger)
|
err := j.Configure([]byte(ts.config), subLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error : %s", err)
|
t.Fatalf("Unexpected error : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualLines := 0
|
actualLines := 0
|
||||||
|
|
||||||
if ts.expectedLines != 0 {
|
if ts.expectedLines != 0 {
|
||||||
go func() {
|
go func() {
|
||||||
READLOOP:
|
READLOOP:
|
||||||
|
@ -240,6 +262,7 @@ journalctl_filter:
|
||||||
|
|
||||||
err = j.StreamingAcquisition(out, &tomb)
|
err = j.StreamingAcquisition(out, &tomb)
|
||||||
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -248,16 +271,20 @@ journalctl_filter:
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
assert.Equal(t, ts.expectedLines, actualLines)
|
assert.Equal(t, ts.expectedLines, actualLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
tomb.Kill(nil)
|
tomb.Kill(nil)
|
||||||
tomb.Wait()
|
tomb.Wait()
|
||||||
|
|
||||||
output, _ := exec.Command("pgrep", "-x", "journalctl").CombinedOutput()
|
output, _ := exec.Command("pgrep", "-x", "journalctl").CombinedOutput()
|
||||||
if string(output) != "" {
|
if string(output) != "" {
|
||||||
t.Fatalf("Found a journalctl process after killing the tomb !")
|
t.Fatalf("Found a journalctl process after killing the tomb !")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.expectedOutput != "" {
|
if ts.expectedOutput != "" {
|
||||||
if hook.LastEntry() == nil {
|
if hook.LastEntry() == nil {
|
||||||
t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
|
t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
|
assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
|
||||||
hook.Reset()
|
hook.Reset()
|
||||||
}
|
}
|
||||||
|
@ -270,5 +297,6 @@ func TestMain(m *testing.M) {
|
||||||
fullPath := filepath.Join(currentDir, "test_files")
|
fullPath := filepath.Join(currentDir, "test_files")
|
||||||
os.Setenv("PATH", fullPath+":"+os.Getenv("PATH"))
|
os.Setenv("PATH", fullPath+":"+os.Getenv("PATH"))
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,24 +79,23 @@ webhook_path: /k8s-audit`,
|
||||||
|
|
||||||
err := f.UnmarshalConfig([]byte(test.config))
|
err := f.UnmarshalConfig([]byte(test.config))
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = f.Configure([]byte(test.config), subLogger)
|
err = f.Configure([]byte(test.config), subLogger)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
f.StreamingAcquisition(out, tb)
|
f.StreamingAcquisition(out, tb)
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
tb.Kill(nil)
|
tb.Kill(nil)
|
||||||
err = tb.Wait()
|
err = tb.Wait()
|
||||||
if test.expectedErr != "" {
|
if test.expectedErr != "" {
|
||||||
assert.ErrorContains(t, err, test.expectedErr)
|
require.ErrorContains(t, err, test.expectedErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
|
@ -252,10 +252,10 @@ webhook_path: /k8s-audit`,
|
||||||
|
|
||||||
f := KubernetesAuditSource{}
|
f := KubernetesAuditSource{}
|
||||||
err := f.UnmarshalConfig([]byte(test.config))
|
err := f.UnmarshalConfig([]byte(test.config))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = f.Configure([]byte(test.config), subLogger)
|
err = f.Configure([]byte(test.config), subLogger)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req := httptest.NewRequest(test.method, "/k8s-audit", strings.NewReader(test.body))
|
req := httptest.NewRequest(test.method, "/k8s-audit", strings.NewReader(test.body))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -268,11 +268,11 @@ webhook_path: /k8s-audit`,
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatusCode, res.StatusCode)
|
assert.Equal(t, test.expectedStatusCode, res.StatusCode)
|
||||||
//time.Sleep(1 * time.Second)
|
//time.Sleep(1 * time.Second)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tb.Kill(nil)
|
tb.Kill(nil)
|
||||||
err = tb.Wait()
|
err = tb.Wait()
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, test.eventCount, eventCount)
|
assert.Equal(t, test.eventCount, eventCount)
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/sys/windows/svc/eventlog"
|
"golang.org/x/sys/windows/svc/eventlog"
|
||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
)
|
)
|
||||||
|
@ -124,7 +125,7 @@ event_level: bla`,
|
||||||
}
|
}
|
||||||
assert.Contains(t, err.Error(), test.expectedErr)
|
assert.Contains(t, err.Error(), test.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectedQuery, q)
|
assert.Equal(t, test.expectedQuery, q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,9 +222,8 @@ event_ids:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if test.expectedLines == nil {
|
if test.expectedLines == nil {
|
||||||
assert.Equal(t, 0, len(linesRead))
|
assert.Empty(t, linesRead)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, len(test.expectedLines), len(linesRead))
|
|
||||||
assert.Equal(t, test.expectedLines, linesRead)
|
assert.Equal(t, test.expectedLines, linesRead)
|
||||||
}
|
}
|
||||||
to.Kill(nil)
|
to.Kill(nil)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewAlertContext(t *testing.T) {
|
func TestNewAlertContext(t *testing.T) {
|
||||||
|
@ -29,8 +30,7 @@ func TestNewAlertContext(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
fmt.Printf("Running test '%s'\n", test.name)
|
fmt.Printf("Running test '%s'\n", test.name)
|
||||||
err := NewAlertContext(test.contextToSend, test.valueLength)
|
err := NewAlertContext(test.contextToSend, test.valueLength)
|
||||||
assert.ErrorIs(t, err, test.expectedErr)
|
require.ErrorIs(t, err, test.expectedErr)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ func TestEventToContext(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
fmt.Printf("Running test '%s'\n", test.name)
|
fmt.Printf("Running test '%s'\n", test.name)
|
||||||
err := NewAlertContext(test.contextToSend, test.valueLength)
|
err := NewAlertContext(test.contextToSend, test.valueLength)
|
||||||
assert.ErrorIs(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
metas, _ := EventToContext(test.events)
|
metas, _ := EventToContext(test.events)
|
||||||
assert.ElementsMatch(t, test.expectedResult, metas)
|
assert.ElementsMatch(t, test.expectedResult, metas)
|
||||||
|
|
|
@ -2,7 +2,9 @@ package alertcontext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
@ -14,6 +16,8 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNoContextData = errors.New("no context to send")
|
||||||
|
|
||||||
// this file is here to avoid circular dependencies between the configuration and the hub
|
// this file is here to avoid circular dependencies between the configuration and the hub
|
||||||
|
|
||||||
// HubItemWrapper is a wrapper around a hub item to unmarshal only the context part
|
// HubItemWrapper is a wrapper around a hub item to unmarshal only the context part
|
||||||
|
@ -25,7 +29,7 @@ type HubItemWrapper struct {
|
||||||
// mergeContext adds the context from src to dest.
|
// mergeContext adds the context from src to dest.
|
||||||
func mergeContext(dest map[string][]string, src map[string][]string) error {
|
func mergeContext(dest map[string][]string, src map[string][]string) error {
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return fmt.Errorf("no context data to merge")
|
return ErrNoContextData
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
|
@ -86,8 +90,9 @@ func addContextFromFile(toSend map[string][]string, filePath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mergeContext(toSend, newContext)
|
err = mergeContext(toSend, newContext)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, ErrNoContextData) {
|
||||||
log.Warningf("while merging context from %s: %s", filePath, err)
|
// having an empty console/context.yaml is not an error
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -125,8 +130,10 @@ func LoadConsoleContext(c *csconfig.Config, hub *cwhub.Hub) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addContextFromFile(c.Crowdsec.ContextToSend, c.Crowdsec.ConsoleContextPath); err != nil {
|
if err := addContextFromFile(c.Crowdsec.ContextToSend, c.Crowdsec.ConsoleContextPath); err != nil {
|
||||||
if !ignoreMissing || !os.IsNotExist(err) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
|
} else if !ignoreMissing {
|
||||||
|
log.Warningf("while merging context from %s: %s", c.Crowdsec.ConsoleContextPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
@ -25,12 +26,11 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -39,19 +39,16 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if r.URL.RawQuery == "ip=1.2.3.4" {
|
if r.URL.RawQuery == "ip=1.2.3.4" {
|
||||||
testMethod(t, r, "GET")
|
testMethod(t, r, "GET")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, `null`)
|
fmt.Fprintf(w, `null`)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,36 +104,26 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
]`)
|
]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
tcapacity := int32(5)
|
|
||||||
tduration := "59m49.264032632s"
|
|
||||||
torigin := "crowdsec"
|
|
||||||
tscenario := "crowdsecurity/ssh-bf"
|
tscenario := "crowdsecurity/ssh-bf"
|
||||||
tscope := "Ip"
|
tscope := "Ip"
|
||||||
ttype := "ban"
|
|
||||||
tvalue := "1.1.1.172"
|
tvalue := "1.1.1.172"
|
||||||
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
||||||
teventscount := int32(6)
|
|
||||||
tleakspeed := "10s"
|
|
||||||
tmessage := "Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"
|
tmessage := "Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"
|
||||||
tscenariohash := "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"
|
|
||||||
tscenarioversion := "0.1"
|
|
||||||
tstartat := "2020-11-28 10:20:46.842701127 +0100 +0100"
|
|
||||||
tstopat := "2020-11-28 10:20:46.845621385 +0100 +0100"
|
|
||||||
|
|
||||||
expected := models.GetAlertsResponse{
|
expected := models.GetAlertsResponse{
|
||||||
&models.Alert{
|
&models.Alert{
|
||||||
Capacity: &tcapacity,
|
Capacity: ptr.Of(int32(5)),
|
||||||
CreatedAt: "2020-11-28T10:20:47+01:00",
|
CreatedAt: "2020-11-28T10:20:47+01:00",
|
||||||
Decisions: []*models.Decision{
|
Decisions: []*models.Decision{
|
||||||
{
|
{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("59m49.264032632s"),
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Origin: &torigin,
|
Origin: ptr.Of("crowdsec"),
|
||||||
Scenario: &tscenario,
|
Scenario: &tscenario,
|
||||||
|
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Simulated: new(bool), //false,
|
Simulated: ptr.Of(false),
|
||||||
Type: &ttype,
|
Type: ptr.Of("ban"),
|
||||||
Value: &tvalue,
|
Value: &tvalue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -167,16 +154,16 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
Timestamp: &ttimestamp,
|
Timestamp: &ttimestamp,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
EventsCount: &teventscount,
|
EventsCount: ptr.Of(int32(6)),
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Leakspeed: &tleakspeed,
|
Leakspeed: ptr.Of("10s"),
|
||||||
MachineID: "test",
|
MachineID: "test",
|
||||||
Message: &tmessage,
|
Message: &tmessage,
|
||||||
Remediation: false,
|
Remediation: false,
|
||||||
Scenario: &tscenario,
|
Scenario: &tscenario,
|
||||||
ScenarioHash: &tscenariohash,
|
ScenarioHash: ptr.Of("4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"),
|
||||||
ScenarioVersion: &tscenarioversion,
|
ScenarioVersion: ptr.Of("0.1"),
|
||||||
Simulated: new(bool), //(false),
|
Simulated: ptr.Of(false),
|
||||||
Source: &models.Source{
|
Source: &models.Source{
|
||||||
AsName: "Cloudflare Inc",
|
AsName: "Cloudflare Inc",
|
||||||
AsNumber: "",
|
AsNumber: "",
|
||||||
|
@ -188,8 +175,8 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Value: &tvalue,
|
Value: &tvalue,
|
||||||
},
|
},
|
||||||
StartAt: &tstartat,
|
StartAt: ptr.Of("2020-11-28 10:20:46.842701127 +0100 +0100"),
|
||||||
StopAt: &tstopat,
|
StopAt: ptr.Of("2020-11-28 10:20:46.845621385 +0100 +0100"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,30 +185,16 @@ func TestAlertsListAsMachine(t *testing.T) {
|
||||||
//log.Debugf("expected : -> %s", spew.Sdump(expected))
|
//log.Debugf("expected : -> %s", spew.Sdump(expected))
|
||||||
//first one returns data
|
//first one returns data
|
||||||
alerts, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
alerts, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Errorf("test Unable to list alerts : %+v", err)
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
}
|
assert.Equal(t, expected, *alerts)
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*alerts, expected) {
|
|
||||||
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
//this one doesn't
|
//this one doesn't
|
||||||
filter := AlertsListOpts{IPEquals: new(string)}
|
filter := AlertsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||||
*filter.IPEquals = "1.2.3.4"
|
|
||||||
|
|
||||||
alerts, resp, err = client.Alerts.List(context.Background(), filter)
|
alerts, resp, err = client.Alerts.List(context.Background(), filter)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Errorf("test Unable to list alerts : %+v", err)
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Empty(t, *alerts)
|
assert.Empty(t, *alerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,9 +209,7 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -247,12 +218,10 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
mux.HandleFunc("/alerts/2", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/alerts/2", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "GET")
|
testMethod(t, r, "GET")
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
@ -312,34 +281,24 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
}`)
|
}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
tcapacity := int32(5)
|
|
||||||
tduration := "59m49.264032632s"
|
|
||||||
torigin := "crowdsec"
|
|
||||||
tscenario := "crowdsecurity/ssh-bf"
|
tscenario := "crowdsecurity/ssh-bf"
|
||||||
tscope := "Ip"
|
tscope := "Ip"
|
||||||
ttype := "ban"
|
ttype := "ban"
|
||||||
tvalue := "1.1.1.172"
|
tvalue := "1.1.1.172"
|
||||||
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
||||||
teventscount := int32(6)
|
|
||||||
tleakspeed := "10s"
|
|
||||||
tmessage := "Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"
|
|
||||||
tscenariohash := "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"
|
|
||||||
tscenarioversion := "0.1"
|
|
||||||
tstartat := "2020-11-28 10:20:46.842701127 +0100 +0100"
|
|
||||||
tstopat := "2020-11-28 10:20:46.845621385 +0100 +0100"
|
|
||||||
|
|
||||||
expected := &models.Alert{
|
expected := &models.Alert{
|
||||||
Capacity: &tcapacity,
|
Capacity: ptr.Of(int32(5)),
|
||||||
CreatedAt: "2020-11-28T10:20:47+01:00",
|
CreatedAt: "2020-11-28T10:20:47+01:00",
|
||||||
Decisions: []*models.Decision{
|
Decisions: []*models.Decision{
|
||||||
{
|
{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("59m49.264032632s"),
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Origin: &torigin,
|
Origin: ptr.Of("crowdsec"),
|
||||||
Scenario: &tscenario,
|
Scenario: &tscenario,
|
||||||
|
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Simulated: new(bool), //false,
|
Simulated: ptr.Of(false),
|
||||||
Type: &ttype,
|
Type: &ttype,
|
||||||
Value: &tvalue,
|
Value: &tvalue,
|
||||||
},
|
},
|
||||||
|
@ -371,16 +330,16 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
Timestamp: &ttimestamp,
|
Timestamp: &ttimestamp,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
EventsCount: &teventscount,
|
EventsCount: ptr.Of(int32(6)),
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Leakspeed: &tleakspeed,
|
Leakspeed: ptr.Of("10s"),
|
||||||
MachineID: "test",
|
MachineID: "test",
|
||||||
Message: &tmessage,
|
Message: ptr.Of("Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"),
|
||||||
Remediation: false,
|
Remediation: false,
|
||||||
Scenario: &tscenario,
|
Scenario: &tscenario,
|
||||||
ScenarioHash: &tscenariohash,
|
ScenarioHash: ptr.Of("4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"),
|
||||||
ScenarioVersion: &tscenarioversion,
|
ScenarioVersion: ptr.Of("0.1"),
|
||||||
Simulated: new(bool), //(false),
|
Simulated: ptr.Of(false),
|
||||||
Source: &models.Source{
|
Source: &models.Source{
|
||||||
AsName: "Cloudflare Inc",
|
AsName: "Cloudflare Inc",
|
||||||
AsNumber: "",
|
AsNumber: "",
|
||||||
|
@ -392,24 +351,18 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Value: &tvalue,
|
Value: &tvalue,
|
||||||
},
|
},
|
||||||
StartAt: &tstartat,
|
StartAt: ptr.Of("2020-11-28 10:20:46.842701127 +0100 +0100"),
|
||||||
StopAt: &tstopat,
|
StopAt: ptr.Of("2020-11-28 10:20:46.845621385 +0100 +0100"),
|
||||||
}
|
}
|
||||||
|
|
||||||
alerts, resp, err := client.Alerts.GetByID(context.Background(), 1)
|
alerts, resp, err := client.Alerts.GetByID(context.Background(), 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, *expected, *alerts)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*alerts, *expected) {
|
|
||||||
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
//fail
|
//fail
|
||||||
_, _, err = client.Alerts.GetByID(context.Background(), 2)
|
_, _, err = client.Alerts.GetByID(context.Background(), 2)
|
||||||
assert.Contains(t, fmt.Sprintf("%s", err), "API error: object not found")
|
cstest.RequireErrorMessage(t, err, "API error: object not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertsCreateAsMachine(t *testing.T) {
|
func TestAlertsCreateAsMachine(t *testing.T) {
|
||||||
|
@ -420,17 +373,17 @@ func TestAlertsCreateAsMachine(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "POST")
|
testMethod(t, r, "POST")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`["3"]`))
|
w.Write([]byte(`["3"]`))
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -439,10 +392,7 @@ func TestAlertsCreateAsMachine(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
|
@ -452,13 +402,8 @@ func TestAlertsCreateAsMachine(t *testing.T) {
|
||||||
|
|
||||||
expected := &models.AddAlertsResponse{"3"}
|
expected := &models.AddAlertsResponse{"3"}
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
assert.Equal(t, *expected, *alerts)
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*alerts, *expected) {
|
|
||||||
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertsDeleteAsMachine(t *testing.T) {
|
func TestAlertsDeleteAsMachine(t *testing.T) {
|
||||||
|
@ -469,18 +414,18 @@ func TestAlertsDeleteAsMachine(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "DELETE")
|
testMethod(t, r, "DELETE")
|
||||||
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`{"message":"0 deleted alerts"}`))
|
w.Write([]byte(`{"message":"0 deleted alerts"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -489,25 +434,16 @@ func TestAlertsDeleteAsMachine(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
alert := AlertsDeleteOpts{IPEquals: new(string)}
|
alert := AlertsDeleteOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||||
*alert.IPEquals = "1.2.3.4"
|
|
||||||
alerts, resp, err := client.Alerts.Delete(context.Background(), alert)
|
alerts, resp, err := client.Alerts.Delete(context.Background(), alert)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := &models.DeleteAlertsResponse{NbDeleted: ""}
|
expected := &models.DeleteAlertsResponse{NbDeleted: ""}
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
assert.Equal(t, *expected, *alerts)
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*alerts, *expected) {
|
|
||||||
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package apiclient
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -13,7 +14,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/fflag"
|
"github.com/crowdsecurity/crowdsec/pkg/fflag"
|
||||||
|
@ -52,10 +52,12 @@ func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
dump, _ := httputil.DumpRequest(req, true)
|
dump, _ := httputil.DumpRequest(req, true)
|
||||||
log.Tracef("auth-api request: %s", string(dump))
|
log.Tracef("auth-api request: %s", string(dump))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the HTTP request.
|
// Make the HTTP request.
|
||||||
resp, err := t.transport().RoundTrip(req)
|
resp, err := t.transport().RoundTrip(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("auth-api: auth with api key failed return nil response, error: %s", err)
|
log.Errorf("auth-api: auth with api key failed return nil response, error: %s", err)
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,10 +117,12 @@ func (r retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||||
for i := 0; i < maxAttempts; i++ {
|
for i := 0; i < maxAttempts; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
if r.withBackOff {
|
if r.withBackOff {
|
||||||
|
//nolint:gosec
|
||||||
backoff += 10 + rand.Intn(20)
|
backoff += 10 + rand.Intn(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("retrying in %d seconds (attempt %d of %d)", backoff, i+1, r.maxAttempts)
|
log.Infof("retrying in %d seconds (attempt %d of %d)", backoff, i+1, r.maxAttempts)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
return resp, req.Context().Err()
|
return resp, req.Context().Err()
|
||||||
|
@ -134,8 +138,7 @@ func (r retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||||
resp, err = r.next.RoundTrip(clonedReq)
|
resp, err = r.next.RoundTrip(clonedReq)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
left := maxAttempts - i - 1
|
if left := maxAttempts - i - 1; left > 0 {
|
||||||
if left > 0 {
|
|
||||||
log.Errorf("error while performing request: %s; %d retries left", err, left)
|
log.Errorf("error while performing request: %s; %d retries left", err, left)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +180,7 @@ func (t *JWTTransport) refreshJwtToken() error {
|
||||||
log.Debugf("scenarios list updated for '%s'", *t.MachineID)
|
log.Debugf("scenarios list updated for '%s'", *t.MachineID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = models.WatcherAuthRequest{
|
auth := models.WatcherAuthRequest{
|
||||||
MachineID: t.MachineID,
|
MachineID: t.MachineID,
|
||||||
Password: t.Password,
|
Password: t.Password,
|
||||||
Scenarios: t.Scenarios,
|
Scenarios: t.Scenarios,
|
||||||
|
@ -264,13 +267,14 @@ func (t *JWTTransport) refreshJwtToken() error {
|
||||||
|
|
||||||
// RoundTrip implements the RoundTripper interface.
|
// RoundTrip implements the RoundTripper interface.
|
||||||
func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
// in a few occasions several goroutines will execute refreshJwtToken concurrently which is useless and will cause overload on CAPI
|
// In a few occasions several goroutines will execute refreshJwtToken concurrently which is useless and will cause overload on CAPI
|
||||||
// we use a mutex to avoid this
|
// we use a mutex to avoid this
|
||||||
//We also bypass the refresh if we are requesting the login endpoint, as it does not require a token, and it leads to do 2 requests instead of one (refresh + actual login request)
|
// We also bypass the refresh if we are requesting the login endpoint, as it does not require a token, and it leads to do 2 requests instead of one (refresh + actual login request)
|
||||||
t.refreshTokenMutex.Lock()
|
t.refreshTokenMutex.Lock()
|
||||||
if req.URL.Path != "/"+t.VersionPrefix+"/watchers/login" && (t.Token == "" || t.Expiration.Add(-time.Minute).Before(time.Now().UTC())) {
|
if req.URL.Path != "/"+t.VersionPrefix+"/watchers/login" && (t.Token == "" || t.Expiration.Add(-time.Minute).Before(time.Now().UTC())) {
|
||||||
if err := t.refreshJwtToken(); err != nil {
|
if err := t.refreshJwtToken(); err != nil {
|
||||||
t.refreshTokenMutex.Unlock()
|
t.refreshTokenMutex.Unlock()
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,8 +300,9 @@ func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
/*we had an error (network error for example, or 401 because token is refused), reset the token ?*/
|
// we had an error (network error for example, or 401 because token is refused), reset the token ?
|
||||||
t.Token = ""
|
t.Token = ""
|
||||||
|
|
||||||
return resp, fmt.Errorf("performing jwt auth: %w", err)
|
return resp, fmt.Errorf("performing jwt auth: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,6 +360,7 @@ func cloneRequest(r *http.Request) *http.Request {
|
||||||
*r2 = *r
|
*r2 = *r
|
||||||
// deep copy of the Header
|
// deep copy of the Header
|
||||||
r2.Header = make(http.Header, len(r.Header))
|
r2.Header = make(http.Header, len(r.Header))
|
||||||
|
|
||||||
for k, s := range r.Header {
|
for k, s := range r.Header {
|
||||||
r2.Header[k] = append([]string(nil), s...)
|
r2.Header[k] = append([]string(nil), s...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
|
|
||||||
|
@ -24,13 +25,11 @@ type BasicMockPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLoginsForMockErrorCases() map[string]int {
|
func getLoginsForMockErrorCases() map[string]int {
|
||||||
loginsForMockErrorCases := map[string]int{
|
return map[string]int{
|
||||||
"login_400": http.StatusBadRequest,
|
"login_400": http.StatusBadRequest,
|
||||||
"login_409": http.StatusConflict,
|
"login_409": http.StatusConflict,
|
||||||
"login_500": http.StatusInternalServerError,
|
"login_500": http.StatusInternalServerError,
|
||||||
}
|
}
|
||||||
|
|
||||||
return loginsForMockErrorCases
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBasicMuxMock(t *testing.T, mux *http.ServeMux, path string) {
|
func initBasicMuxMock(t *testing.T, mux *http.ServeMux, path string) {
|
||||||
|
@ -49,7 +48,7 @@ func initBasicMuxMock(t *testing.T, mux *http.ServeMux, path string) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseBody := ""
|
var responseBody string
|
||||||
responseCode, hasFoundErrorMock := loginsForMockErrorCases[payload.MachineID]
|
responseCode, hasFoundErrorMock := loginsForMockErrorCases[payload.MachineID]
|
||||||
|
|
||||||
if !hasFoundErrorMock {
|
if !hasFoundErrorMock {
|
||||||
|
@ -58,6 +57,7 @@ func initBasicMuxMock(t *testing.T, mux *http.ServeMux, path string) {
|
||||||
} else {
|
} else {
|
||||||
responseBody = fmt.Sprintf("Error %d", responseCode)
|
responseBody = fmt.Sprintf("Error %d", responseCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("MockServerReceived > %s // Login : [%s] => Mux response [%d]", newStr, payload.MachineID, responseCode)
|
log.Printf("MockServerReceived > %s // Login : [%s] => Mux response [%d]", newStr, payload.MachineID, responseCode)
|
||||||
|
|
||||||
w.WriteHeader(responseCode)
|
w.WriteHeader(responseCode)
|
||||||
|
@ -76,14 +76,13 @@ func TestWatcherRegister(t *testing.T) {
|
||||||
|
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
//body: models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password}
|
//body: models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password}
|
||||||
initBasicMuxMock(t, mux, "/watchers")
|
initBasicMuxMock(t, mux, "/watchers")
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid Registration : should retrieve the client and no err
|
// Valid Registration : should retrieve the client and no err
|
||||||
clientconfig := Config{
|
clientconfig := Config{
|
||||||
|
@ -95,9 +94,7 @@ func TestWatcherRegister(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := RegisterClient(&clientconfig, &http.Client{})
|
client, err := RegisterClient(&clientconfig, &http.Client{})
|
||||||
if client == nil || err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("while registering client : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("->%T", client)
|
log.Printf("->%T", client)
|
||||||
|
|
||||||
|
@ -107,11 +104,8 @@ func TestWatcherRegister(t *testing.T) {
|
||||||
clientconfig.MachineID = fmt.Sprintf("login_%d", errorCodeToTest)
|
clientconfig.MachineID = fmt.Sprintf("login_%d", errorCodeToTest)
|
||||||
|
|
||||||
client, err = RegisterClient(&clientconfig, &http.Client{})
|
client, err = RegisterClient(&clientconfig, &http.Client{})
|
||||||
if client != nil || err == nil {
|
require.Nil(t, client, "nil expected for the response code %d", errorCodeToTest)
|
||||||
t.Fatalf("The RegisterClient function should have returned an error for the response code %d", errorCodeToTest)
|
require.Error(t, err, "error expected for the response code %d", errorCodeToTest)
|
||||||
} else {
|
|
||||||
log.Printf("The RegisterClient function handled the error code %d as expected \n\r", errorCodeToTest)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,9 +120,7 @@ func TestWatcherAuth(t *testing.T) {
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok auth
|
//ok auth
|
||||||
clientConfig := &Config{
|
clientConfig := &Config{
|
||||||
|
@ -139,34 +131,27 @@ func TestWatcherAuth(t *testing.T) {
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
Scenarios: []string{"crowdsecurity/test"},
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
}
|
}
|
||||||
client, err := NewClient(clientConfig)
|
|
||||||
|
|
||||||
if err != nil {
|
client, err := NewClient(clientConfig)
|
||||||
t.Fatalf("new api client: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
_, _, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
||||||
MachineID: &clientConfig.MachineID,
|
MachineID: &clientConfig.MachineID,
|
||||||
Password: &clientConfig.Password,
|
Password: &clientConfig.Password,
|
||||||
Scenarios: clientConfig.Scenarios,
|
Scenarios: clientConfig.Scenarios,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unexpect auth err 0: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Testing error handling on AuthenticateWatcher (400, 409): should retrieve an error
|
// Testing error handling on AuthenticateWatcher (400, 409): should retrieve an error
|
||||||
// Not testing 500 because it loops and try to re-autehnticate. But you can test it manually by adding it in array
|
// Not testing 500 because it loops and try to re-autehnticate. But you can test it manually by adding it in array
|
||||||
errorCodesToTest := [2]int{http.StatusBadRequest, http.StatusConflict}
|
errorCodesToTest := [2]int{http.StatusBadRequest, http.StatusConflict}
|
||||||
for _, errorCodeToTest := range errorCodesToTest {
|
for _, errorCodeToTest := range errorCodesToTest {
|
||||||
clientConfig.MachineID = fmt.Sprintf("login_%d", errorCodeToTest)
|
clientConfig.MachineID = fmt.Sprintf("login_%d", errorCodeToTest)
|
||||||
|
|
||||||
client, err := NewClient(clientConfig)
|
client, err := NewClient(clientConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if err != nil {
|
_, resp, err := client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp *Response
|
|
||||||
_, resp, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
|
||||||
MachineID: &clientConfig.MachineID,
|
MachineID: &clientConfig.MachineID,
|
||||||
Password: &clientConfig.Password,
|
Password: &clientConfig.Password,
|
||||||
})
|
})
|
||||||
|
@ -175,9 +160,7 @@ func TestWatcherAuth(t *testing.T) {
|
||||||
resp.Response.Body.Close()
|
resp.Response.Body.Close()
|
||||||
|
|
||||||
bodyBytes, err := io.ReadAll(resp.Response.Body)
|
bodyBytes, err := io.ReadAll(resp.Response.Body)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("error while reading body: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(string(bodyBytes))
|
log.Printf(string(bodyBytes))
|
||||||
t.Fatalf("The AuthenticateWatcher function should have returned an error for the response code %d", errorCodeToTest)
|
t.Fatalf("The AuthenticateWatcher function should have returned an error for the response code %d", errorCodeToTest)
|
||||||
|
@ -199,10 +182,12 @@ func TestWatcherUnregister(t *testing.T) {
|
||||||
assert.Equal(t, int64(0), r.ContentLength)
|
assert.Equal(t, int64(0), r.ContentLength)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "POST")
|
testMethod(t, r, "POST")
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
_, _ = buf.ReadFrom(r.Body)
|
_, _ = buf.ReadFrom(r.Body)
|
||||||
|
|
||||||
newStr := buf.String()
|
newStr := buf.String()
|
||||||
if newStr == `{"machine_id":"test_login","password":"test_password","scenarios":["crowdsecurity/test"]}
|
if newStr == `{"machine_id":"test_login","password":"test_password","scenarios":["crowdsecurity/test"]}
|
||||||
` {
|
` {
|
||||||
|
@ -217,9 +202,7 @@ func TestWatcherUnregister(t *testing.T) {
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
mycfg := &Config{
|
mycfg := &Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -229,16 +212,12 @@ func TestWatcherUnregister(t *testing.T) {
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
Scenarios: []string{"crowdsecurity/test"},
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
}
|
}
|
||||||
client, err := NewClient(mycfg)
|
|
||||||
|
|
||||||
if err != nil {
|
client, err := NewClient(mycfg)
|
||||||
t.Fatalf("new api client: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Auth.UnregisterWatcher(context.Background())
|
_, err = client.Auth.UnregisterWatcher(context.Background())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("while registering client : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("->%T", client)
|
log.Printf("->%T", client)
|
||||||
}
|
}
|
||||||
|
@ -255,6 +234,7 @@ func TestWatcherEnroll(t *testing.T) {
|
||||||
_, _ = buf.ReadFrom(r.Body)
|
_, _ = buf.ReadFrom(r.Body)
|
||||||
newStr := buf.String()
|
newStr := buf.String()
|
||||||
log.Debugf("body -> %s", newStr)
|
log.Debugf("body -> %s", newStr)
|
||||||
|
|
||||||
if newStr == `{"attachment_key":"goodkey","name":"","tags":[],"overwrite":false}
|
if newStr == `{"attachment_key":"goodkey","name":"","tags":[],"overwrite":false}
|
||||||
` {
|
` {
|
||||||
log.Print("good key")
|
log.Print("good key")
|
||||||
|
@ -266,17 +246,17 @@ func TestWatcherEnroll(t *testing.T) {
|
||||||
fmt.Fprintf(w, `{"message":"the attachment key provided is not valid"}`)
|
fmt.Fprintf(w, `{"message":"the attachment key provided is not valid"}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "POST")
|
testMethod(t, r, "POST")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, `{"code":200,"expire":"2029-11-30T14:14:24+01:00","token":"toto"}`)
|
fmt.Fprintf(w, `{"code":200,"expire":"2029-11-30T14:14:24+01:00","token":"toto"}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
mycfg := &Config{
|
mycfg := &Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -286,16 +266,12 @@ func TestWatcherEnroll(t *testing.T) {
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
Scenarios: []string{"crowdsecurity/test"},
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
}
|
}
|
||||||
client, err := NewClient(mycfg)
|
|
||||||
|
|
||||||
if err != nil {
|
client, err := NewClient(mycfg)
|
||||||
t.Fatalf("new api client: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Auth.EnrollWatcher(context.Background(), "goodkey", "", []string{}, false)
|
_, err = client.Auth.EnrollWatcher(context.Background(), "goodkey", "", []string{}, false)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unexpect enroll err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Auth.EnrollWatcher(context.Background(), "badkey", "", []string{}, false)
|
_, err = client.Auth.EnrollWatcher(context.Background(), "badkey", "", []string{}, false)
|
||||||
assert.Contains(t, err.Error(), "the attachment key provided is not valid", "got %s", err.Error())
|
assert.Contains(t, err.Error(), "the attachment key provided is not valid", "got %s", err.Error())
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApiAuth(t *testing.T) {
|
func TestApiAuth(t *testing.T) {
|
||||||
|
@ -17,6 +20,7 @@ func TestApiAuth(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "GET")
|
testMethod(t, r, "GET")
|
||||||
|
|
||||||
if r.Header.Get("X-Api-Key") == "ixu" {
|
if r.Header.Get("X-Api-Key") == "ixu" {
|
||||||
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -26,11 +30,11 @@ func TestApiAuth(t *testing.T) {
|
||||||
w.Write([]byte(`{"message":"access forbidden"}`))
|
w.Write([]byte(`{"message":"access forbidden"}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
|
@ -40,18 +44,12 @@ func TestApiAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
alert := DecisionsListOpts{IPEquals: new(string)}
|
|
||||||
*alert.IPEquals = "1.2.3.4"
|
|
||||||
_, resp, err := newcli.Decisions.List(context.Background(), alert)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
alert := DecisionsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
_, resp, err := newcli.Decisions.List(context.Background(), alert)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
|
|
||||||
//ko bad token
|
//ko bad token
|
||||||
auth = &APIKeyTransport{
|
auth = &APIKeyTransport{
|
||||||
|
@ -59,25 +57,21 @@ func TestApiAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err = newcli.Decisions.List(context.Background(), alert)
|
_, resp, err = newcli.Decisions.List(context.Background(), alert)
|
||||||
|
|
||||||
log.Infof("--> %s", err)
|
log.Infof("--> %s", err)
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusForbidden {
|
assert.Equal(t, http.StatusForbidden, resp.Response.StatusCode)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
cstest.RequireErrorMessage(t, err, "API error: access forbidden")
|
||||||
|
|
||||||
assert.Contains(t, err.Error(), "API error: access forbidden")
|
|
||||||
//ko empty token
|
//ko empty token
|
||||||
auth = &APIKeyTransport{}
|
auth = &APIKeyTransport{}
|
||||||
|
|
||||||
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = newcli.Decisions.List(context.Background(), alert)
|
_, _, err = newcli.Decisions.List(context.Background(), alert)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
|
@ -74,6 +74,7 @@ func NewClient(config *Config) (*ApiClient, error) {
|
||||||
VersionPrefix: config.VersionPrefix,
|
VersionPrefix: config.VersionPrefix,
|
||||||
UpdateScenario: config.UpdateScenario,
|
UpdateScenario: config.UpdateScenario,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
tlsconfig := tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
||||||
tlsconfig.RootCAs = CaCertPool
|
tlsconfig.RootCAs = CaCertPool
|
||||||
|
|
||||||
|
@ -180,8 +181,7 @@ func (e *ErrorResponse) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResponse(r *http.Response) *Response {
|
func newResponse(r *http.Response) *Response {
|
||||||
response := &Response{Response: r}
|
return &Response{Response: r}
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckResponse(r *http.Response) error {
|
func CheckResponse(r *http.Response) error {
|
||||||
|
@ -192,7 +192,7 @@ func CheckResponse(r *http.Response) error {
|
||||||
errorResponse := &ErrorResponse{}
|
errorResponse := &ErrorResponse{}
|
||||||
|
|
||||||
data, err := io.ReadAll(r.Body)
|
data, err := io.ReadAll(r.Body)
|
||||||
if err == nil && data != nil {
|
if err == nil && len(data)>0 {
|
||||||
err := json.Unmarshal(data, errorResponse)
|
err := json.Unmarshal(data, errorResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("http code %d, invalid body: %w", r.StatusCode, err)
|
return fmt.Errorf("http code %d, invalid body: %w", r.StatusCode, err)
|
||||||
|
|
|
@ -8,19 +8,19 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewRequestInvalid(t *testing.T) {
|
func TestNewRequestInvalid(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
//missing slash in uri
|
//missing slash in uri
|
||||||
apiURL, err := url.Parse(urlx)
|
apiURL, err := url.Parse(urlx)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -29,9 +29,8 @@ func TestNewRequestInvalid(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
@ -44,17 +43,16 @@ func TestNewRequestInvalid(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
assert.Contains(t, err.Error(), `building request: BaseURL must have a trailing slash, but `)
|
cstest.RequireErrorContains(t, err, "building request: BaseURL must have a trailing slash, but ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRequestTimeout(t *testing.T) {
|
func TestNewRequestTimeout(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
//missing slash in uri
|
|
||||||
|
// missing slash in uri
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
|
@ -63,9 +61,8 @@ func TestNewRequestTimeout(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
@ -75,5 +72,5 @@ func TestNewRequestTimeout(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, _, err = client.Alerts.List(ctx, AlertsListOpts{})
|
_, _, err = client.Alerts.List(ctx, AlertsListOpts{})
|
||||||
assert.Contains(t, err.Error(), `performing request: context deadline exceeded`)
|
cstest.RequireErrorMessage(t, err, "performing request: context deadline exceeded")
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,13 +22,13 @@ import (
|
||||||
- each test will then bind handler for the method(s) they want to try
|
- each test will then bind handler for the method(s) they want to try
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
|
func setup() (*http.ServeMux, string, func()) {
|
||||||
return setupWithPrefix("v1")
|
return setupWithPrefix("v1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupWithPrefix(urlPrefix string) (mux *http.ServeMux, serverURL string, teardown func()) {
|
func setupWithPrefix(urlPrefix string) (*http.ServeMux, string, func()) {
|
||||||
// mux is the HTTP request multiplexer used with the test server.
|
// mux is the HTTP request multiplexer used with the test server.
|
||||||
mux = http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
baseURLPath := "/" + urlPrefix
|
baseURLPath := "/" + urlPrefix
|
||||||
|
|
||||||
apiHandler := http.NewServeMux()
|
apiHandler := http.NewServeMux()
|
||||||
|
@ -40,19 +42,16 @@ func setupWithPrefix(urlPrefix string) (mux *http.ServeMux, serverURL string, te
|
||||||
|
|
||||||
func testMethod(t *testing.T, r *http.Request, want string) {
|
func testMethod(t *testing.T, r *http.Request, want string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
assert.Equal(t, want, r.Method)
|
||||||
if got := r.Method; got != want {
|
|
||||||
t.Errorf("Request method: %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientOk(t *testing.T) {
|
func TestNewClientOk(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -60,9 +59,8 @@ func TestNewClientOk(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -75,22 +73,17 @@ func TestNewClientOk(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
_, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
_, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("test Unable to list alerts : %+v", err)
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Fatalf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusCreated)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientKo(t *testing.T) {
|
func TestNewClientKo(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -98,9 +91,8 @@ func TestNewClientKo(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
@ -113,36 +105,36 @@ func TestNewClientKo(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
assert.Contains(t, err.Error(), `API error: bad login/password`)
|
cstest.RequireErrorContains(t, err, `API error: bad login/password`)
|
||||||
|
|
||||||
log.Printf("err-> %s", err)
|
log.Printf("err-> %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDefaultClient(t *testing.T) {
|
func TestNewDefaultClient(t *testing.T) {
|
||||||
mux, urlx, teardown := setup()
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
client, err := NewDefaultClient(apiURL, "/v1", "", nil)
|
client, err := NewDefaultClient(apiURL, "/v1", "", nil)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(`{"code": 401, "message" : "brr"}`))
|
w.Write([]byte(`{"code": 401, "message" : "brr"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
assert.Contains(t, err.Error(), `performing request: API error: brr`)
|
cstest.RequireErrorMessage(t, err, "performing request: API error: brr")
|
||||||
|
|
||||||
log.Printf("err-> %s", err)
|
log.Printf("err-> %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientRegisterKO(t *testing.T) {
|
func TestNewClientRegisterKO(t *testing.T) {
|
||||||
apiURL, err := url.Parse("http://127.0.0.1:4242/")
|
apiURL, err := url.Parse("http://127.0.0.1:4242/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
_, err = RegisterClient(&Config{
|
_, err = RegisterClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -150,17 +142,18 @@ func TestNewClientRegisterKO(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
}, &http.Client{})
|
}, &http.Client{})
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
assert.Contains(t, fmt.Sprintf("%s", err), "dial tcp 127.0.0.1:4242: connect: connection refused")
|
cstest.RequireErrorContains(t, err, "dial tcp 127.0.0.1:4242: connect: connection refused")
|
||||||
} else {
|
} else {
|
||||||
assert.Contains(t, fmt.Sprintf("%s", err), " No connection could be made because the target machine actively refused it.")
|
cstest.RequireErrorContains(t, err, " No connection could be made because the target machine actively refused it.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientRegisterOK(t *testing.T) {
|
func TestNewClientRegisterOK(t *testing.T) {
|
||||||
log.SetLevel(log.TraceLevel)
|
log.SetLevel(log.TraceLevel)
|
||||||
mux, urlx, teardown := setup()
|
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
|
@ -171,9 +164,8 @@ func TestNewClientRegisterOK(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
client, err := RegisterClient(&Config{
|
client, err := RegisterClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -181,17 +173,15 @@ func TestNewClientRegisterOK(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
}, &http.Client{})
|
}, &http.Client{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("while registering client : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("->%T", client)
|
log.Printf("->%T", client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientBadAnswer(t *testing.T) {
|
func TestNewClientBadAnswer(t *testing.T) {
|
||||||
log.SetLevel(log.TraceLevel)
|
log.SetLevel(log.TraceLevel)
|
||||||
mux, urlx, teardown := setup()
|
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
/*mock login*/
|
/*mock login*/
|
||||||
|
@ -200,10 +190,10 @@ func TestNewClientBadAnswer(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(`bad`))
|
w.Write([]byte(`bad`))
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
_, err = RegisterClient(&Config{
|
_, err = RegisterClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -211,5 +201,5 @@ func TestNewClientBadAnswer(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
}, &http.Client{})
|
}, &http.Client{})
|
||||||
assert.Contains(t, fmt.Sprintf("%s", err), `invalid body: invalid character 'b' looking for beginning of value`)
|
cstest.RequireErrorContains(t, err, "invalid body: invalid character 'b' looking for beginning of value")
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,8 @@ func (s *DecisionsService) GetDecisionsFromBlocklist(ctx context.Context, blockl
|
||||||
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
log.Debugf("[URL] %s %s", req.Method, req.URL)
|
log.Debugf("[URL] %s %s", req.Method, req.URL)
|
||||||
// we dont use client_http Do method because we need the reader and is not provided. We would be forced to use Pipe and goroutine, etc
|
// we don't use client_http Do method because we need the reader and is not provided.
|
||||||
|
// We would be forced to use Pipe and goroutine, etc
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if resp != nil && resp.Body != nil {
|
if resp != nil && resp.Body != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
@ -216,6 +217,7 @@ func (s *DecisionsService) GetDecisionsFromBlocklist(ctx context.Context, blockl
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
log.Debugf("Received nok status code %d for blocklist %s", resp.StatusCode, *blocklist.URL)
|
log.Debugf("Received nok status code %d for blocklist %s", resp.StatusCode, *blocklist.URL)
|
||||||
|
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
|
|
||||||
|
@ -38,10 +38,9 @@ func TestDecisionsList(t *testing.T) {
|
||||||
//no results
|
//no results
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok answer
|
//ok answer
|
||||||
auth := &APIKeyTransport{
|
auth := &APIKeyTransport{
|
||||||
|
@ -49,55 +48,32 @@ func TestDecisionsList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tduration := "3h59m55.756182786s"
|
|
||||||
torigin := "cscli"
|
|
||||||
tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
|
|
||||||
tscope := "Ip"
|
|
||||||
ttype := "ban"
|
|
||||||
tvalue := "1.2.3.4"
|
|
||||||
expected := &models.GetDecisionsResponse{
|
expected := &models.GetDecisionsResponse{
|
||||||
&models.Decision{
|
&models.Decision{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("3h59m55.756182786s"),
|
||||||
ID: 4,
|
ID: 4,
|
||||||
Origin: &torigin,
|
Origin: ptr.Of("cscli"),
|
||||||
Scenario: &tscenario,
|
Scenario: ptr.Of("manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"),
|
||||||
Scope: &tscope,
|
Scope: ptr.Of("Ip"),
|
||||||
Type: &ttype,
|
Type: ptr.Of("ban"),
|
||||||
Value: &tvalue,
|
Value: ptr.Of("1.2.3.4"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//OK decisions
|
// OK decisions
|
||||||
decisionsFilter := DecisionsListOpts{IPEquals: new(string)}
|
decisionsFilter := DecisionsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||||
*decisionsFilter.IPEquals = "1.2.3.4"
|
|
||||||
decisions, resp, err := newcli.Decisions.List(context.Background(), decisionsFilter)
|
decisions, resp, err := newcli.Decisions.List(context.Background(), decisionsFilter)
|
||||||
|
require.NoError(t, err)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
assert.Equal(t, *expected, *decisions)
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*decisions, *expected) {
|
|
||||||
t.Fatalf("returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Empty return
|
//Empty return
|
||||||
decisionsFilter = DecisionsListOpts{IPEquals: new(string)}
|
decisionsFilter = DecisionsListOpts{IPEquals: ptr.Of("1.2.3.5")}
|
||||||
*decisionsFilter.IPEquals = "1.2.3.5"
|
|
||||||
decisions, resp, err = newcli.Decisions.List(context.Background(), decisionsFilter)
|
decisions, resp, err = newcli.Decisions.List(context.Background(), decisionsFilter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Empty(t, *decisions)
|
assert.Empty(t, *decisions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +96,7 @@ func TestDecisionsStream(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
||||||
assert.Equal(t, "ixu", r.Header.Get("X-Api-Key"))
|
assert.Equal(t, "ixu", r.Header.Get("X-Api-Key"))
|
||||||
testMethod(t, r, http.MethodDelete)
|
testMethod(t, r, http.MethodDelete)
|
||||||
|
@ -129,9 +106,7 @@ func TestDecisionsStream(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok answer
|
//ok answer
|
||||||
auth := &APIKeyTransport{
|
auth := &APIKeyTransport{
|
||||||
|
@ -139,63 +114,38 @@ func TestDecisionsStream(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tduration := "3h59m55.756182786s"
|
|
||||||
torigin := "cscli"
|
|
||||||
tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
|
|
||||||
tscope := "Ip"
|
|
||||||
ttype := "ban"
|
|
||||||
tvalue := "1.2.3.4"
|
|
||||||
expected := &models.DecisionsStreamResponse{
|
expected := &models.DecisionsStreamResponse{
|
||||||
New: models.GetDecisionsResponse{
|
New: models.GetDecisionsResponse{
|
||||||
&models.Decision{
|
&models.Decision{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("3h59m55.756182786s"),
|
||||||
ID: 4,
|
ID: 4,
|
||||||
Origin: &torigin,
|
Origin: ptr.Of("cscli"),
|
||||||
Scenario: &tscenario,
|
Scenario: ptr.Of("manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"),
|
||||||
Scope: &tscope,
|
Scope: ptr.Of("Ip"),
|
||||||
Type: &ttype,
|
Type: ptr.Of("ban"),
|
||||||
Value: &tvalue,
|
Value: ptr.Of("1.2.3.4"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
|
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, *expected, *decisions)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*decisions, *expected) {
|
|
||||||
t.Fatalf("returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
//and second call, we get empty lists
|
//and second call, we get empty lists
|
||||||
decisions, resp, err = newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: false})
|
decisions, resp, err = newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: false})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Empty(t, decisions.New)
|
assert.Empty(t, decisions.New)
|
||||||
assert.Empty(t, decisions.Deleted)
|
assert.Empty(t, decisions.Deleted)
|
||||||
|
|
||||||
//delete stream
|
//delete stream
|
||||||
resp, err = newcli.Decisions.StopStream(context.Background())
|
resp, err = newcli.Decisions.StopStream(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
||||||
|
@ -219,9 +169,7 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok answer
|
//ok answer
|
||||||
auth := &APIKeyTransport{
|
auth := &APIKeyTransport{
|
||||||
|
@ -229,38 +177,30 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tduration := "3h59m55.756182786s"
|
|
||||||
torigin := "CAPI"
|
torigin := "CAPI"
|
||||||
tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
|
|
||||||
tscope := "ip"
|
tscope := "ip"
|
||||||
ttype := "ban"
|
ttype := "ban"
|
||||||
tvalue := "1.2.3.4"
|
|
||||||
tvalue1 := "1.2.3.5"
|
|
||||||
tscenarioDeleted := "deleted"
|
|
||||||
tdurationDeleted := "1h"
|
|
||||||
expected := &models.DecisionsStreamResponse{
|
expected := &models.DecisionsStreamResponse{
|
||||||
New: models.GetDecisionsResponse{
|
New: models.GetDecisionsResponse{
|
||||||
&models.Decision{
|
&models.Decision{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("3h59m55.756182786s"),
|
||||||
Origin: &torigin,
|
Origin: &torigin,
|
||||||
Scenario: &tscenario,
|
Scenario: ptr.Of("manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"),
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Type: &ttype,
|
Type: &ttype,
|
||||||
Value: &tvalue,
|
Value: ptr.Of("1.2.3.4"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Deleted: models.GetDecisionsResponse{
|
Deleted: models.GetDecisionsResponse{
|
||||||
&models.Decision{
|
&models.Decision{
|
||||||
Duration: &tdurationDeleted,
|
Duration: ptr.Of("1h"),
|
||||||
Origin: &torigin,
|
Origin: &torigin,
|
||||||
Scenario: &tscenarioDeleted,
|
Scenario: ptr.Of("deleted"),
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Type: &ttype,
|
Type: &ttype,
|
||||||
Value: &tvalue1,
|
Value: ptr.Of("1.2.3.5"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -268,18 +208,8 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
||||||
// GetStream is supposed to consume v3 payload and return v2 response
|
// GetStream is supposed to consume v3 payload and return v2 response
|
||||||
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
|
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, *expected, *decisions)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*decisions, *expected) {
|
|
||||||
t.Fatalf("returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecisionsStreamV3(t *testing.T) {
|
func TestDecisionsStreamV3(t *testing.T) {
|
||||||
|
@ -300,9 +230,7 @@ func TestDecisionsStreamV3(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok answer
|
//ok answer
|
||||||
auth := &APIKeyTransport{
|
auth := &APIKeyTransport{
|
||||||
|
@ -310,30 +238,19 @@ func TestDecisionsStreamV3(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tduration := "3h59m55.756182786s"
|
|
||||||
tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
|
|
||||||
tscope := "ip"
|
tscope := "ip"
|
||||||
tvalue := "1.2.3.4"
|
|
||||||
tvalue1 := "1.2.3.5"
|
|
||||||
tdurationBlocklist := "24h"
|
|
||||||
tnameBlocklist := "blocklist1"
|
|
||||||
tremediationBlocklist := "ban"
|
|
||||||
tscopeBlocklist := "ip"
|
|
||||||
turlBlocklist := "/v3/blocklist"
|
|
||||||
expected := &modelscapi.GetDecisionsStreamResponse{
|
expected := &modelscapi.GetDecisionsStreamResponse{
|
||||||
New: modelscapi.GetDecisionsStreamResponseNew{
|
New: modelscapi.GetDecisionsStreamResponseNew{
|
||||||
&modelscapi.GetDecisionsStreamResponseNewItem{
|
&modelscapi.GetDecisionsStreamResponseNewItem{
|
||||||
Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
|
Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
|
||||||
{
|
{
|
||||||
Duration: &tduration,
|
Duration: ptr.Of("3h59m55.756182786s"),
|
||||||
Value: &tvalue,
|
Value: ptr.Of("1.2.3.4"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Scenario: &tscenario,
|
Scenario: ptr.Of("manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"),
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -341,18 +258,18 @@ func TestDecisionsStreamV3(t *testing.T) {
|
||||||
&modelscapi.GetDecisionsStreamResponseDeletedItem{
|
&modelscapi.GetDecisionsStreamResponseDeletedItem{
|
||||||
Scope: &tscope,
|
Scope: &tscope,
|
||||||
Decisions: []string{
|
Decisions: []string{
|
||||||
tvalue1,
|
"1.2.3.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Links: &modelscapi.GetDecisionsStreamResponseLinks{
|
Links: &modelscapi.GetDecisionsStreamResponseLinks{
|
||||||
Blocklists: []*modelscapi.BlocklistLink{
|
Blocklists: []*modelscapi.BlocklistLink{
|
||||||
{
|
{
|
||||||
Duration: &tdurationBlocklist,
|
Duration: ptr.Of("24h"),
|
||||||
Name: &tnameBlocklist,
|
Name: ptr.Of("blocklist1"),
|
||||||
Remediation: &tremediationBlocklist,
|
Remediation: ptr.Of("ban"),
|
||||||
Scope: &tscopeBlocklist,
|
Scope: ptr.Of("ip"),
|
||||||
URL: &turlBlocklist,
|
URL: ptr.Of("/v3/blocklist"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -361,18 +278,8 @@ func TestDecisionsStreamV3(t *testing.T) {
|
||||||
// GetStream is supposed to consume v3 payload and return v2 response
|
// GetStream is supposed to consume v3 payload and return v2 response
|
||||||
decisions, resp, err := newcli.Decisions.GetStreamV3(context.Background(), DecisionsStreamOpts{Startup: true})
|
decisions, resp, err := newcli.Decisions.GetStreamV3(context.Background(), DecisionsStreamOpts{Startup: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
assert.Equal(t, *expected, *decisions)
|
||||||
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*decisions, *expected) {
|
|
||||||
t.Fatalf("returned %+v, want %+v", resp, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecisionsFromBlocklist(t *testing.T) {
|
func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
|
@ -383,10 +290,13 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
|
|
||||||
mux.HandleFunc("/blocklist", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/blocklist", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, http.MethodGet)
|
testMethod(t, r, http.MethodGet)
|
||||||
|
|
||||||
if r.Header.Get("If-Modified-Since") == "Sun, 01 Jan 2023 01:01:01 GMT" {
|
if r.Header.Get("If-Modified-Since") == "Sun, 01 Jan 2023 01:01:01 GMT" {
|
||||||
w.WriteHeader(http.StatusNotModified)
|
w.WriteHeader(http.StatusNotModified)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("1.2.3.4\r\n1.2.3.5"))
|
w.Write([]byte("1.2.3.4\r\n1.2.3.5"))
|
||||||
|
@ -394,9 +304,7 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
//ok answer
|
//ok answer
|
||||||
auth := &APIKeyTransport{
|
auth := &APIKeyTransport{
|
||||||
|
@ -404,12 +312,8 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tvalue1 := "1.2.3.4"
|
|
||||||
tvalue2 := "1.2.3.5"
|
|
||||||
tdurationBlocklist := "24h"
|
tdurationBlocklist := "24h"
|
||||||
tnameBlocklist := "blocklist1"
|
tnameBlocklist := "blocklist1"
|
||||||
tremediationBlocklist := "ban"
|
tremediationBlocklist := "ban"
|
||||||
|
@ -419,7 +323,7 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
expected := []*models.Decision{
|
expected := []*models.Decision{
|
||||||
{
|
{
|
||||||
Duration: &tdurationBlocklist,
|
Duration: &tdurationBlocklist,
|
||||||
Value: &tvalue1,
|
Value: ptr.Of("1.2.3.4"),
|
||||||
Scenario: &tnameBlocklist,
|
Scenario: &tnameBlocklist,
|
||||||
Scope: &tscopeBlocklist,
|
Scope: &tscopeBlocklist,
|
||||||
Type: &tremediationBlocklist,
|
Type: &tremediationBlocklist,
|
||||||
|
@ -427,7 +331,7 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Duration: &tdurationBlocklist,
|
Duration: &tdurationBlocklist,
|
||||||
Value: &tvalue2,
|
Value: ptr.Of("1.2.3.5"),
|
||||||
Scenario: &tnameBlocklist,
|
Scenario: &tnameBlocklist,
|
||||||
Scope: &tscopeBlocklist,
|
Scope: &tscopeBlocklist,
|
||||||
Type: &tremediationBlocklist,
|
Type: &tremediationBlocklist,
|
||||||
|
@ -450,13 +354,7 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
log.Infof("expected : %s, %s, %s, %s, %s", *expected[0].Value, *expected[0].Duration, *expected[0].Scenario, *expected[0].Scope, *expected[0].Type)
|
log.Infof("expected : %s, %s, %s, %s, %s", *expected[0].Value, *expected[0].Duration, *expected[0].Scenario, *expected[0].Scope, *expected[0].Type)
|
||||||
log.Infof("decisions: %s, %s, %s, %s, %s", *decisions[1].Value, *decisions[1].Duration, *decisions[1].Scenario, *decisions[1].Scope, *decisions[1].Type)
|
log.Infof("decisions: %s, %s, %s, %s, %s", *decisions[1].Value, *decisions[1].Duration, *decisions[1].Scenario, *decisions[1].Scope, *decisions[1].Type)
|
||||||
|
|
||||||
if err != nil {
|
assert.Equal(t, expected, decisions)
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(decisions, expected) {
|
|
||||||
t.Fatalf("returned %+v, want %+v", decisions, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test cache control
|
// test cache control
|
||||||
_, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
|
_, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
|
||||||
|
@ -466,8 +364,10 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
Name: &tnameBlocklist,
|
Name: &tnameBlocklist,
|
||||||
Duration: &tdurationBlocklist,
|
Duration: &tdurationBlocklist,
|
||||||
}, ptr.Of("Sun, 01 Jan 2023 01:01:01 GMT"))
|
}, ptr.Of("Sun, 01 Jan 2023 01:01:01 GMT"))
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, isModified)
|
assert.False(t, isModified)
|
||||||
|
|
||||||
_, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
|
_, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
|
||||||
URL: &turlBlocklist,
|
URL: &turlBlocklist,
|
||||||
Scope: &tscopeBlocklist,
|
Scope: &tscopeBlocklist,
|
||||||
|
@ -475,6 +375,7 @@ func TestDecisionsFromBlocklist(t *testing.T) {
|
||||||
Name: &tnameBlocklist,
|
Name: &tnameBlocklist,
|
||||||
Duration: &tdurationBlocklist,
|
Duration: &tdurationBlocklist,
|
||||||
}, ptr.Of("Mon, 02 Jan 2023 01:01:01 GMT"))
|
}, ptr.Of("Mon, 02 Jan 2023 01:01:01 GMT"))
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, isModified)
|
assert.True(t, isModified)
|
||||||
}
|
}
|
||||||
|
@ -485,6 +386,7 @@ func TestDeleteDecisions(t *testing.T) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "DELETE")
|
testMethod(t, r, "DELETE")
|
||||||
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
assert.Equal(t, "ip=1.2.3.4", r.URL.RawQuery)
|
||||||
|
@ -492,11 +394,12 @@ func TestDeleteDecisions(t *testing.T) {
|
||||||
w.Write([]byte(`{"nbDeleted":"1"}`))
|
w.Write([]byte(`{"nbDeleted":"1"}`))
|
||||||
//w.Write([]byte(`{"message":"0 deleted alerts"}`))
|
//w.Write([]byte(`{"message":"0 deleted alerts"}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("URL is %s", urlx)
|
log.Printf("URL is %s", urlx)
|
||||||
|
|
||||||
apiURL, err := url.Parse(urlx + "/")
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("parsing api url: %s", apiURL)
|
|
||||||
}
|
|
||||||
client, err := NewClient(&Config{
|
client, err := NewClient(&Config{
|
||||||
MachineID: "test_login",
|
MachineID: "test_login",
|
||||||
Password: "test_password",
|
Password: "test_password",
|
||||||
|
@ -504,18 +407,13 @@ func TestDeleteDecisions(t *testing.T) {
|
||||||
URL: apiURL,
|
URL: apiURL,
|
||||||
VersionPrefix: "v1",
|
VersionPrefix: "v1",
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("new api client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filters := DecisionsDeleteOpts{IPEquals: new(string)}
|
filters := DecisionsDeleteOpts{IPEquals: new(string)}
|
||||||
*filters.IPEquals = "1.2.3.4"
|
*filters.IPEquals = "1.2.3.4"
|
||||||
deleted, _, err := client.Decisions.Delete(context.Background(), filters)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
deleted, _, err := client.Decisions.Delete(context.Background(), filters)
|
||||||
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "1", deleted.NbDeleted)
|
assert.Equal(t, "1", deleted.NbDeleted)
|
||||||
|
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
@ -530,22 +428,23 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
|
||||||
ScenariosContaining string
|
ScenariosContaining string
|
||||||
ScenariosNotContaining string
|
ScenariosNotContaining string
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
fields fields
|
||||||
want string
|
expected string
|
||||||
wantErr bool
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no filter",
|
name: "no filter",
|
||||||
want: baseURLString + "?",
|
expected: baseURLString + "?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "startup=true",
|
name: "startup=true",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
Startup: true,
|
Startup: true,
|
||||||
},
|
},
|
||||||
want: baseURLString + "?startup=true",
|
expected: baseURLString + "?startup=true",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "set all params",
|
name: "set all params",
|
||||||
|
@ -555,7 +454,7 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
|
||||||
ScenariosContaining: "ssh",
|
ScenariosContaining: "ssh",
|
||||||
ScenariosNotContaining: "bf",
|
ScenariosNotContaining: "bf",
|
||||||
},
|
},
|
||||||
want: baseURLString + "?scenarios_containing=ssh&scenarios_not_containing=bf&scopes=ip%2Crange&startup=true",
|
expected: baseURLString + "?scenarios_containing=ssh&scenarios_not_containing=bf&scopes=ip%2Crange&startup=true",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,25 +467,20 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
|
||||||
ScenariosContaining: tt.fields.ScenariosContaining,
|
ScenariosContaining: tt.fields.ScenariosContaining,
|
||||||
ScenariosNotContaining: tt.fields.ScenariosNotContaining,
|
ScenariosNotContaining: tt.fields.ScenariosNotContaining,
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := o.addQueryParamsToURL(baseURLString)
|
got, err := o.addQueryParamsToURL(baseURLString)
|
||||||
if (err != nil) != tt.wantErr {
|
cstest.RequireErrorContains(t, err, tt.expectedErr)
|
||||||
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() error = %v, wantErr %v", err, tt.wantErr)
|
if tt.expectedErr != "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gotURL, err := url.Parse(got)
|
gotURL, err := url.Parse(got)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedURL, err := url.Parse(tt.want)
|
expectedURL, err := url.Parse(tt.expected)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *gotURL != *expectedURL {
|
assert.Equal(t, *expectedURL, *gotURL)
|
||||||
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() = %v, want %v", *gotURL, *expectedURL)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,13 @@ func (h *HeartBeatService) StartHeartBeat(ctx context.Context, t *tomb.Tomb) {
|
||||||
select {
|
select {
|
||||||
case <-hbTimer.C:
|
case <-hbTimer.C:
|
||||||
log.Debug("heartbeat: sending heartbeat")
|
log.Debug("heartbeat: sending heartbeat")
|
||||||
|
|
||||||
ok, resp, err := h.Ping(ctx)
|
ok, resp, err := h.Ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("heartbeat error : %s", err)
|
log.Errorf("heartbeat error : %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Response.Body.Close()
|
resp.Response.Body.Close()
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
log.Errorf("heartbeat unexpected return code : %d", resp.Response.StatusCode)
|
log.Errorf("heartbeat unexpected return code : %d", resp.Response.StatusCode)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
||||||
|
@ -22,21 +22,14 @@ type LAPI struct {
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
loginResp models.WatcherAuthResponse
|
loginResp models.WatcherAuthResponse
|
||||||
bouncerKey string
|
bouncerKey string
|
||||||
t *testing.T
|
|
||||||
DBConfig *csconfig.DatabaseCfg
|
DBConfig *csconfig.DatabaseCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLAPITest(t *testing.T) LAPI {
|
func SetupLAPITest(t *testing.T) LAPI {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
router, loginResp, config, err := InitMachineTest(t)
|
router, loginResp, config := InitMachineTest(t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
APIKey, err := CreateTestBouncer(config.API.Server.DbConfig)
|
APIKey := CreateTestBouncer(t, config.API.Server.DbConfig)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return LAPI{
|
return LAPI{
|
||||||
router: router,
|
router: router,
|
||||||
|
@ -46,24 +39,23 @@ func SetupLAPITest(t *testing.T) LAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LAPI) InsertAlertFromFile(path string) *httptest.ResponseRecorder {
|
func (l *LAPI) InsertAlertFromFile(t *testing.T, path string) *httptest.ResponseRecorder {
|
||||||
alertReader := GetAlertReaderFromFile(path)
|
alertReader := GetAlertReaderFromFile(t, path)
|
||||||
return l.RecordResponse(http.MethodPost, "/v1/alerts", alertReader, "password")
|
return l.RecordResponse(t, http.MethodPost, "/v1/alerts", alertReader, "password")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LAPI) RecordResponse(verb string, url string, body *strings.Reader, authType string) *httptest.ResponseRecorder {
|
func (l *LAPI) RecordResponse(t *testing.T, verb string, url string, body *strings.Reader, authType string) *httptest.ResponseRecorder {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, err := http.NewRequest(verb, url, body)
|
req, err := http.NewRequest(verb, url, body)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
l.t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if authType == "apikey" {
|
switch authType {
|
||||||
|
case "apikey":
|
||||||
req.Header.Add("X-Api-Key", l.bouncerKey)
|
req.Header.Add("X-Api-Key", l.bouncerKey)
|
||||||
} else if authType == "password" {
|
case "password":
|
||||||
AddAuthHeaders(req, l.loginResp)
|
AddAuthHeaders(req, l.loginResp)
|
||||||
} else {
|
default:
|
||||||
l.t.Fatal("auth type not supported")
|
t.Fatal("auth type not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
l.router.ServeHTTP(w, req)
|
l.router.ServeHTTP(w, req)
|
||||||
|
@ -71,29 +63,16 @@ func (l *LAPI) RecordResponse(verb string, url string, body *strings.Reader, aut
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitMachineTest(t *testing.T) (*gin.Engine, models.WatcherAuthResponse, csconfig.Config, error) {
|
func InitMachineTest(t *testing.T) (*gin.Engine, models.WatcherAuthResponse, csconfig.Config) {
|
||||||
router, config, err := NewAPITest(t)
|
router, config := NewAPITest(t)
|
||||||
if err != nil {
|
loginResp := LoginToTestAPI(t, router, config)
|
||||||
return nil, models.WatcherAuthResponse{}, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
loginResp, err := LoginToTestAPI(router, config)
|
return router, loginResp, config
|
||||||
if err != nil {
|
|
||||||
return nil, models.WatcherAuthResponse{}, config, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return router, loginResp, config, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginToTestAPI(router *gin.Engine, config csconfig.Config) (models.WatcherAuthResponse, error) {
|
func LoginToTestAPI(t *testing.T, router *gin.Engine, config csconfig.Config) models.WatcherAuthResponse {
|
||||||
body, err := CreateTestMachine(router)
|
body := CreateTestMachine(t, router)
|
||||||
if err != nil {
|
ValidateMachine(t, "test", config.API.Server.DbConfig)
|
||||||
return models.WatcherAuthResponse{}, err
|
|
||||||
}
|
|
||||||
err = ValidateMachine("test", config.API.Server.DbConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(body))
|
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(body))
|
||||||
|
@ -101,12 +80,10 @@ func LoginToTestAPI(router *gin.Engine, config csconfig.Config) (models.WatcherA
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
loginResp := models.WatcherAuthResponse{}
|
loginResp := models.WatcherAuthResponse{}
|
||||||
err = json.NewDecoder(w.Body).Decode(&loginResp)
|
err := json.NewDecoder(w.Body).Decode(&loginResp)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return models.WatcherAuthResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginResp, nil
|
return loginResp
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthResponse) {
|
func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthResponse) {
|
||||||
|
@ -116,17 +93,17 @@ func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthRespon
|
||||||
|
|
||||||
func TestSimulatedAlert(t *testing.T) {
|
func TestSimulatedAlert(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
lapi.InsertAlertFromFile("./tests/alert_minibulk+simul.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_minibulk+simul.json")
|
||||||
alertContent := GetAlertReaderFromFile("./tests/alert_minibulk+simul.json")
|
alertContent := GetAlertReaderFromFile(t, "./tests/alert_minibulk+simul.json")
|
||||||
//exclude decision in simulation mode
|
//exclude decision in simulation mode
|
||||||
|
|
||||||
w := lapi.RecordResponse("GET", "/v1/alerts?simulated=false", alertContent, "password")
|
w := lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=false", alertContent, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
||||||
assert.NotContains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
assert.NotContains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
||||||
//include decision in simulation mode
|
//include decision in simulation mode
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=true", alertContent, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=true", alertContent, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
||||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
||||||
|
@ -136,35 +113,29 @@ func TestCreateAlert(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
// Create Alert with invalid format
|
// Create Alert with invalid format
|
||||||
|
|
||||||
w := lapi.RecordResponse(http.MethodPost, "/v1/alerts", strings.NewReader("test"), "password")
|
w := lapi.RecordResponse(t, http.MethodPost, "/v1/alerts", strings.NewReader("test"), "password")
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"invalid character 'e' in literal true (expecting 'r')\"}", w.Body.String())
|
assert.Equal(t, `{"message":"invalid character 'e' in literal true (expecting 'r')"}`, w.Body.String())
|
||||||
|
|
||||||
// Create Alert with invalid input
|
// Create Alert with invalid input
|
||||||
alertContent := GetAlertReaderFromFile("./tests/invalidAlert_sample.json")
|
alertContent := GetAlertReaderFromFile(t, "./tests/invalidAlert_sample.json")
|
||||||
|
|
||||||
w = lapi.RecordResponse(http.MethodPost, "/v1/alerts", alertContent, "password")
|
w = lapi.RecordResponse(t, http.MethodPost, "/v1/alerts", alertContent, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"validation failure list:\\n0.scenario in body is required\\n0.scenario_hash in body is required\\n0.scenario_version in body is required\\n0.simulated in body is required\\n0.source in body is required\"}", w.Body.String())
|
assert.Equal(t, `{"message":"validation failure list:\n0.scenario in body is required\n0.scenario_hash in body is required\n0.scenario_version in body is required\n0.simulated in body is required\n0.source in body is required"}`, w.Body.String())
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
w = lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
w = lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "[\"1\"]", w.Body.String())
|
assert.Equal(t, `["1"]`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateAlertChannels(t *testing.T) {
|
func TestCreateAlertChannels(t *testing.T) {
|
||||||
apiServer, config, err := NewAPIServer(t)
|
apiServer, config := NewAPIServer(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
apiServer.controller.PluginChannel = make(chan csplugin.ProfileAlert)
|
apiServer.controller.PluginChannel = make(chan csplugin.ProfileAlert)
|
||||||
apiServer.InitController()
|
apiServer.InitController()
|
||||||
|
|
||||||
loginResp, err := LoginToTestAPI(apiServer.router, config)
|
loginResp := LoginToTestAPI(t, apiServer.router, config)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
lapi := LAPI{router: apiServer.router, loginResp: loginResp}
|
lapi := LAPI{router: apiServer.router, loginResp: loginResp}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -180,7 +151,7 @@ func TestCreateAlertChannels(t *testing.T) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go lapi.InsertAlertFromFile("./tests/alert_ssh-bf.json")
|
go lapi.InsertAlertFromFile(t, "./tests/alert_ssh-bf.json")
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
assert.Len(t, pd.Alert.Decisions, 1)
|
assert.Len(t, pd.Alert.Decisions, 1)
|
||||||
apiServer.Close()
|
apiServer.Close()
|
||||||
|
@ -188,18 +159,18 @@ func TestCreateAlertChannels(t *testing.T) {
|
||||||
|
|
||||||
func TestAlertListFilters(t *testing.T) {
|
func TestAlertListFilters(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
lapi.InsertAlertFromFile("./tests/alert_ssh-bf.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_ssh-bf.json")
|
||||||
alertContent := GetAlertReaderFromFile("./tests/alert_ssh-bf.json")
|
alertContent := GetAlertReaderFromFile(t, "./tests/alert_ssh-bf.json")
|
||||||
|
|
||||||
//bad filter
|
//bad filter
|
||||||
|
|
||||||
w := lapi.RecordResponse("GET", "/v1/alerts?test=test", alertContent, "password")
|
w := lapi.RecordResponse(t, "GET", "/v1/alerts?test=test", alertContent, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String())
|
assert.Equal(t, `{"message":"Filter parameter 'test' is unknown (=test): invalid filter"}`, w.Body.String())
|
||||||
|
|
||||||
//get without filters
|
//get without filters
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
//check alert and decision
|
//check alert and decision
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
|
@ -207,149 +178,149 @@ func TestAlertListFilters(t *testing.T) {
|
||||||
|
|
||||||
//test decision_type filter (ok)
|
//test decision_type filter (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?decision_type=ban", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?decision_type=ban", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test decision_type filter (bad value)
|
//test decision_type filter (bad value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?decision_type=ratata", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?decision_type=ratata", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test scope (ok)
|
//test scope (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?scope=Ip", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?scope=Ip", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test scope (bad value)
|
//test scope (bad value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?scope=rarara", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?scope=rarara", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test scenario (ok)
|
//test scenario (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test scenario (bad value)
|
//test scenario (bad value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?scenario=crowdsecurity/nope", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?scenario=crowdsecurity/nope", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test ip (ok)
|
//test ip (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=91.121.79.195", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=91.121.79.195", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test ip (bad value)
|
//test ip (bad value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=99.122.77.195", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=99.122.77.195", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test ip (invalid value)
|
//test ip (invalid value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=gruueq", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?ip=gruueq", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, `{"message":"unable to convert 'gruueq' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
assert.Equal(t, `{"message":"unable to convert 'gruueq' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
||||||
|
|
||||||
//test range (ok)
|
//test range (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=91.121.79.0/24&contains=false", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=91.121.79.0/24&contains=false", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test range
|
//test range
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=99.122.77.0/24&contains=false", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=99.122.77.0/24&contains=false", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test range (invalid value)
|
//test range (invalid value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=ratata", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?range=ratata", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, `{"message":"unable to convert 'ratata' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
assert.Equal(t, `{"message":"unable to convert 'ratata' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
||||||
|
|
||||||
//test since (ok)
|
//test since (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1h", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1h", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test since (ok but yields no results)
|
//test since (ok but yields no results)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1ns", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1ns", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test since (invalid value)
|
//test since (invalid value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1zuzu", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?since=1zuzu", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
||||||
|
|
||||||
//test until (ok)
|
//test until (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1ns", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1ns", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test until (ok but no return)
|
//test until (ok but no return)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1m", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1m", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test until (invalid value)
|
//test until (invalid value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1zuzu", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?until=1zuzu", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
||||||
|
|
||||||
//test simulated (ok)
|
//test simulated (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=true", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=true", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test simulated (ok)
|
//test simulated (ok)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=false", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?simulated=false", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test has active decision
|
//test has active decision
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=true", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=true", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||||
|
|
||||||
//test has active decision
|
//test has active decision
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=false", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=false", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "null", w.Body.String())
|
assert.Equal(t, "null", w.Body.String())
|
||||||
|
|
||||||
//test has active decision (invalid value)
|
//test has active decision (invalid value)
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=ratatqata", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts?has_active_decision=ratatqata", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, `{"message":"'ratatqata' is not a boolean: strconv.ParseBool: parsing \"ratatqata\": invalid syntax: unable to parse type"}`, w.Body.String())
|
assert.Equal(t, `{"message":"'ratatqata' is not a boolean: strconv.ParseBool: parsing \"ratatqata\": invalid syntax: unable to parse type"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -357,32 +328,32 @@ func TestAlertListFilters(t *testing.T) {
|
||||||
func TestAlertBulkInsert(t *testing.T) {
|
func TestAlertBulkInsert(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
//insert a bulk of 20 alerts to trigger bulk insert
|
//insert a bulk of 20 alerts to trigger bulk insert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_bulk.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_bulk.json")
|
||||||
alertContent := GetAlertReaderFromFile("./tests/alert_bulk.json")
|
alertContent := GetAlertReaderFromFile(t, "./tests/alert_bulk.json")
|
||||||
|
|
||||||
w := lapi.RecordResponse("GET", "/v1/alerts", alertContent, "password")
|
w := lapi.RecordResponse(t, "GET", "/v1/alerts", alertContent, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListAlert(t *testing.T) {
|
func TestListAlert(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
// List Alert with invalid filter
|
// List Alert with invalid filter
|
||||||
|
|
||||||
w := lapi.RecordResponse("GET", "/v1/alerts?test=test", emptyBody, "password")
|
w := lapi.RecordResponse(t, "GET", "/v1/alerts?test=test", emptyBody, "password")
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String())
|
assert.Equal(t, `{"message":"Filter parameter 'test' is unknown (=test): invalid filter"}`, w.Body.String())
|
||||||
|
|
||||||
// List Alert
|
// List Alert
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/alerts", emptyBody, "password")
|
w = lapi.RecordResponse(t, "GET", "/v1/alerts", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "crowdsecurity/test")
|
assert.Contains(t, w.Body.String(), "crowdsecurity/test")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateAlertErrors(t *testing.T) {
|
func TestCreateAlertErrors(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
alertContent := GetAlertReaderFromFile("./tests/alert_sample.json")
|
alertContent := GetAlertReaderFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
//test invalid bearer
|
//test invalid bearer
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -403,7 +374,7 @@ func TestCreateAlertErrors(t *testing.T) {
|
||||||
|
|
||||||
func TestDeleteAlert(t *testing.T) {
|
func TestDeleteAlert(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
// Fail Delete Alert
|
// Fail Delete Alert
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -426,7 +397,7 @@ func TestDeleteAlert(t *testing.T) {
|
||||||
|
|
||||||
func TestDeleteAlertByID(t *testing.T) {
|
func TestDeleteAlertByID(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
// Fail Delete Alert
|
// Fail Delete Alert
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -454,25 +425,18 @@ func TestDeleteAlertTrustedIPS(t *testing.T) {
|
||||||
cfg.API.Server.TrustedIPs = []string{"1.2.3.4", "1.2.4.0/24"}
|
cfg.API.Server.TrustedIPs = []string{"1.2.3.4", "1.2.4.0/24"}
|
||||||
cfg.API.Server.ListenURI = "::8080"
|
cfg.API.Server.ListenURI = "::8080"
|
||||||
server, err := NewServer(cfg.API.Server)
|
server, err := NewServer(cfg.API.Server)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = server.InitController()
|
err = server.InitController()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
router, err := server.Router()
|
router, err := server.Router()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
loginResp := LoginToTestAPI(t, router, cfg)
|
||||||
loginResp, err := LoginToTestAPI(router, cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
lapi := LAPI{
|
lapi := LAPI{
|
||||||
router: router,
|
router: router,
|
||||||
loginResp: loginResp,
|
loginResp: loginResp,
|
||||||
t: t,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertAlertDeleteFailedFromIP := func(ip string) {
|
assertAlertDeleteFailedFromIP := func(ip string) {
|
||||||
|
@ -498,17 +462,17 @@ func TestDeleteAlertTrustedIPS(t *testing.T) {
|
||||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assertAlertDeleteFailedFromIP("4.3.2.1")
|
assertAlertDeleteFailedFromIP("4.3.2.1")
|
||||||
assertAlertDeletedFromIP("1.2.3.4")
|
assertAlertDeletedFromIP("1.2.3.4")
|
||||||
|
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assertAlertDeletedFromIP("1.2.4.0")
|
assertAlertDeletedFromIP("1.2.4.0")
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assertAlertDeletedFromIP("1.2.4.1")
|
assertAlertDeletedFromIP("1.2.4.1")
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assertAlertDeletedFromIP("1.2.4.255")
|
assertAlertDeletedFromIP("1.2.4.255")
|
||||||
|
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
assertAlertDeletedFromIP("127.0.0.1")
|
assertAlertDeletedFromIP("127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIKey(t *testing.T) {
|
func TestAPIKey(t *testing.T) {
|
||||||
router, config, err := NewAPITest(t)
|
router, config := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
APIKey := CreateTestBouncer(t, config.API.Server.DbConfig)
|
||||||
}
|
|
||||||
|
|
||||||
APIKey, err := CreateTestBouncer(config.API.Server.DbConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// Login with empty token
|
// Login with empty token
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodGet, "/v1/decisions", strings.NewReader(""))
|
req, _ := http.NewRequest(http.MethodGet, "/v1/decisions", strings.NewReader(""))
|
||||||
|
@ -27,7 +21,7 @@ func TestAPIKey(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 403, w.Code)
|
assert.Equal(t, 403, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"access forbidden\"}", w.Body.String())
|
assert.Equal(t, `{"message":"access forbidden"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with invalid token
|
// Login with invalid token
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
|
@ -37,7 +31,7 @@ func TestAPIKey(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 403, w.Code)
|
assert.Equal(t, 403, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"access forbidden\"}", w.Body.String())
|
assert.Equal(t, `{"message":"access forbidden"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with valid token
|
// Login with valid token
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
|
|
||||||
func getDBClient(t *testing.T) *database.Client {
|
func getDBClient(t *testing.T) *database.Client {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
dbPath, err := os.CreateTemp("", "*sqlite")
|
dbPath, err := os.CreateTemp("", "*sqlite")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dbClient, err := database.NewClient(&csconfig.DatabaseCfg{
|
dbClient, err := database.NewClient(&csconfig.DatabaseCfg{
|
||||||
|
@ -72,8 +73,9 @@ func getAPIC(t *testing.T) *apic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func absDiff(a int, b int) (c int) {
|
func absDiff(a int, b int) int {
|
||||||
if c = a - b; c < 0 {
|
c := a - b
|
||||||
|
if c < 0 {
|
||||||
return -1 * c
|
return -1 * c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +187,7 @@ func TestAPICFetchScenariosListFromDB(t *testing.T) {
|
||||||
|
|
||||||
func TestNewAPIC(t *testing.T) {
|
func TestNewAPIC(t *testing.T) {
|
||||||
var testConfig *csconfig.OnlineApiClientCfg
|
var testConfig *csconfig.OnlineApiClientCfg
|
||||||
|
|
||||||
setConfig := func() {
|
setConfig := func() {
|
||||||
testConfig = &csconfig.OnlineApiClientCfg{
|
testConfig = &csconfig.OnlineApiClientCfg{
|
||||||
Credentials: &csconfig.ApiCredentialsCfg{
|
Credentials: &csconfig.ApiCredentialsCfg{
|
||||||
|
@ -199,6 +202,7 @@ func TestNewAPIC(t *testing.T) {
|
||||||
dbClient *database.Client
|
dbClient *database.Client
|
||||||
consoleConfig *csconfig.ConsoleConfig
|
consoleConfig *csconfig.ConsoleConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
|
@ -374,7 +378,6 @@ func TestAPICGetMetrics(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, tc.expectedMetric.Bouncers, foundMetrics.Bouncers)
|
assert.Equal(t, tc.expectedMetric.Bouncers, foundMetrics.Bouncers)
|
||||||
assert.Equal(t, tc.expectedMetric.Machines, foundMetrics.Machines)
|
assert.Equal(t, tc.expectedMetric.Machines, foundMetrics.Machines)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,6 +406,7 @@ func TestCreateAlertsForDecision(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
decisions []*models.Decision
|
decisions []*models.Decision
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
|
@ -489,6 +493,7 @@ func TestFillAlertsWithDecisions(t *testing.T) {
|
||||||
alerts []*models.Alert
|
alerts []*models.Alert
|
||||||
decisions []*models.Decision
|
decisions []*models.Decision
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
|
@ -544,26 +549,18 @@ func TestAPICWhitelists(t *testing.T) {
|
||||||
api := getAPIC(t)
|
api := getAPIC(t)
|
||||||
//one whitelist on IP, one on CIDR
|
//one whitelist on IP, one on CIDR
|
||||||
api.whitelists = &csconfig.CapiWhitelist{}
|
api.whitelists = &csconfig.CapiWhitelist{}
|
||||||
ipwl1 := "9.2.3.4"
|
api.whitelists.Ips = append(api.whitelists.Ips, net.ParseIP("9.2.3.4"), net.ParseIP("7.2.3.4"))
|
||||||
ip := net.ParseIP(ipwl1)
|
|
||||||
api.whitelists.Ips = append(api.whitelists.Ips, ip)
|
_, tnet, err := net.ParseCIDR("13.2.3.0/24")
|
||||||
ipwl1 = "7.2.3.4"
|
require.NoError(t, err)
|
||||||
ip = net.ParseIP(ipwl1)
|
|
||||||
api.whitelists.Ips = append(api.whitelists.Ips, ip)
|
|
||||||
cidrwl1 := "13.2.3.0/24"
|
|
||||||
_, tnet, err := net.ParseCIDR(cidrwl1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to parse cidr : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
|
api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
|
||||||
cidrwl1 = "11.2.3.0/24"
|
|
||||||
_, tnet, err = net.ParseCIDR(cidrwl1)
|
_, tnet, err = net.ParseCIDR("11.2.3.0/24")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to parse cidr : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
|
api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
|
||||||
|
|
||||||
api.dbClient.Ent.Decision.Create().
|
api.dbClient.Ent.Decision.Create().
|
||||||
SetOrigin(types.CAPIOrigin).
|
SetOrigin(types.CAPIOrigin).
|
||||||
SetType("ban").
|
SetType("ban").
|
||||||
|
@ -663,12 +660,15 @@ func TestAPICWhitelists(t *testing.T) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
|
||||||
200, "1.2.3.6",
|
200, "1.2.3.6",
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
|
||||||
200, "1.2.3.7",
|
200, "1.2.3.7",
|
||||||
))
|
))
|
||||||
|
|
||||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -801,12 +801,15 @@ func TestAPICPullTop(t *testing.T) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
|
||||||
200, "1.2.3.6",
|
200, "1.2.3.6",
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
|
||||||
200, "1.2.3.7",
|
200, "1.2.3.7",
|
||||||
))
|
))
|
||||||
|
|
||||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -828,7 +831,8 @@ func TestAPICPullTop(t *testing.T) {
|
||||||
alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
|
alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
|
||||||
validDecisions := api.dbClient.Ent.Decision.Query().Where(
|
validDecisions := api.dbClient.Ent.Decision.Query().Where(
|
||||||
decision.UntilGT(time.Now())).
|
decision.UntilGT(time.Now())).
|
||||||
AllX(context.Background())
|
AllX(context.Background(),
|
||||||
|
)
|
||||||
|
|
||||||
decisionScenarioFreq := make(map[string]int)
|
decisionScenarioFreq := make(map[string]int)
|
||||||
alertScenario := make(map[string]int)
|
alertScenario := make(map[string]int)
|
||||||
|
@ -858,6 +862,7 @@ func TestAPICPullTopBLCacheFirstCall(t *testing.T) {
|
||||||
|
|
||||||
httpmock.Activate()
|
httpmock.Activate()
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
|
||||||
200, jsonMarshalX(
|
200, jsonMarshalX(
|
||||||
modelscapi.GetDecisionsStreamResponse{
|
modelscapi.GetDecisionsStreamResponse{
|
||||||
|
@ -887,10 +892,12 @@ func TestAPICPullTopBLCacheFirstCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
|
||||||
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
||||||
return httpmock.NewStringResponse(200, "1.2.3.4"), nil
|
return httpmock.NewStringResponse(200, "1.2.3.4"), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -916,6 +923,7 @@ func TestAPICPullTopBLCacheFirstCall(t *testing.T) {
|
||||||
assert.NotEqual(t, "", req.Header.Get("If-Modified-Since"))
|
assert.NotEqual(t, "", req.Header.Get("If-Modified-Since"))
|
||||||
return httpmock.NewStringResponse(304, ""), nil
|
return httpmock.NewStringResponse(304, ""), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
err = api.PullTop(false)
|
err = api.PullTop(false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
secondLastPullTimestamp, err := api.dbClient.GetConfigItem(blocklistConfigItemName)
|
secondLastPullTimestamp, err := api.dbClient.GetConfigItem(blocklistConfigItemName)
|
||||||
|
@ -928,6 +936,7 @@ func TestAPICPullTopBLCacheForceCall(t *testing.T) {
|
||||||
|
|
||||||
httpmock.Activate()
|
httpmock.Activate()
|
||||||
defer httpmock.DeactivateAndReset()
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
// create a decision about to expire. It should force fetch
|
// create a decision about to expire. It should force fetch
|
||||||
alertInstance := api.dbClient.Ent.Alert.
|
alertInstance := api.dbClient.Ent.Alert.
|
||||||
Create().
|
Create().
|
||||||
|
@ -975,10 +984,12 @@ func TestAPICPullTopBLCacheForceCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
|
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
|
||||||
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
||||||
return httpmock.NewStringResponse(304, ""), nil
|
return httpmock.NewStringResponse(304, ""), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1005,6 +1016,7 @@ func TestAPICPullBlocklistCall(t *testing.T) {
|
||||||
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
|
||||||
return httpmock.NewStringResponse(200, "1.2.3.4"), nil
|
return httpmock.NewStringResponse(200, "1.2.3.4"), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1073,6 +1085,7 @@ func TestAPICPush(t *testing.T) {
|
||||||
Source: &models.Source{},
|
Source: &models.Source{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return alerts
|
return alerts
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -307,6 +307,7 @@ func (s *APIServer) Run(apiReady chan bool) error {
|
||||||
log.Errorf("capi push: %s", err)
|
log.Errorf("capi push: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -315,6 +316,7 @@ func (s *APIServer) Run(apiReady chan bool) error {
|
||||||
log.Errorf("capi pull: %s", err)
|
log.Errorf("capi pull: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -328,6 +330,7 @@ func (s *APIServer) Run(apiReady chan bool) error {
|
||||||
log.Errorf("papi pull: %s", err)
|
log.Errorf("papi pull: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -336,6 +339,7 @@ func (s *APIServer) Run(apiReady chan bool) error {
|
||||||
log.Errorf("capi decisions sync: %s", err)
|
log.Errorf("capi decisions sync: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -13,11 +13,12 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/cstest"
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
"github.com/crowdsecurity/go-cs-lib/version"
|
"github.com/crowdsecurity/go-cs-lib/version"
|
||||||
|
|
||||||
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
||||||
|
@ -63,13 +64,14 @@ func LoadTestConfig(t *testing.T) csconfig.Config {
|
||||||
ShareCustomScenarios: new(bool),
|
ShareCustomScenarios: new(bool),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
apiConfig := csconfig.APICfg{
|
apiConfig := csconfig.APICfg{
|
||||||
Server: &apiServerConfig,
|
Server: &apiServerConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
config.API = &apiConfig
|
config.API = &apiConfig
|
||||||
if err := config.API.Server.LoadProfiles(); err != nil {
|
err := config.API.Server.LoadProfiles()
|
||||||
log.Fatalf("failed to load profiles: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -106,110 +108,89 @@ func LoadTestConfigForwardedFor(t *testing.T) csconfig.Config {
|
||||||
Server: &apiServerConfig,
|
Server: &apiServerConfig,
|
||||||
}
|
}
|
||||||
config.API = &apiConfig
|
config.API = &apiConfig
|
||||||
if err := config.API.Server.LoadProfiles(); err != nil {
|
err := config.API.Server.LoadProfiles()
|
||||||
log.Fatalf("failed to load profiles: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIServer(t *testing.T) (*APIServer, csconfig.Config, error) {
|
func NewAPIServer(t *testing.T) (*APIServer, csconfig.Config) {
|
||||||
config := LoadTestConfig(t)
|
config := LoadTestConfig(t)
|
||||||
|
|
||||||
os.Remove("./ent")
|
os.Remove("./ent")
|
||||||
|
|
||||||
apiServer, err := NewServer(config.API.Server)
|
apiServer, err := NewServer(config.API.Server)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Creating new API server")
|
log.Printf("Creating new API server")
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
return apiServer, config, nil
|
return apiServer, config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPITest(t *testing.T) (*gin.Engine, csconfig.Config, error) {
|
func NewAPITest(t *testing.T) (*gin.Engine, csconfig.Config) {
|
||||||
apiServer, config, err := NewAPIServer(t)
|
apiServer, config := NewAPIServer(t)
|
||||||
if err != nil {
|
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
err := apiServer.InitController()
|
||||||
}
|
require.NoError(t, err)
|
||||||
err = apiServer.InitController()
|
|
||||||
if err != nil {
|
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
router, err := apiServer.Router()
|
router, err := apiServer.Router()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return router, config, nil
|
return router, config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPITestForwardedFor(t *testing.T) (*gin.Engine, csconfig.Config, error) {
|
func NewAPITestForwardedFor(t *testing.T) (*gin.Engine, csconfig.Config) {
|
||||||
config := LoadTestConfigForwardedFor(t)
|
config := LoadTestConfigForwardedFor(t)
|
||||||
|
|
||||||
os.Remove("./ent")
|
os.Remove("./ent")
|
||||||
|
|
||||||
apiServer, err := NewServer(config.API.Server)
|
apiServer, err := NewServer(config.API.Server)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
err = apiServer.InitController()
|
err = apiServer.InitController()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Creating new API server")
|
log.Printf("Creating new API server")
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
router, err := apiServer.Router()
|
router, err := apiServer.Router()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, config, fmt.Errorf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return router, config, nil
|
return router, config
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateMachine(machineID string, config *csconfig.DatabaseCfg) error {
|
func ValidateMachine(t *testing.T, machineID string, config *csconfig.DatabaseCfg) {
|
||||||
dbClient, err := database.NewClient(config)
|
dbClient, err := database.NewClient(config)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return fmt.Errorf("unable to create new database client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbClient.ValidateMachine(machineID); err != nil {
|
err = dbClient.ValidateMachine(machineID)
|
||||||
return fmt.Errorf("unable to validate machine: %s", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMachineIP(machineID string, config *csconfig.DatabaseCfg) (string, error) {
|
func GetMachineIP(t *testing.T, machineID string, config *csconfig.DatabaseCfg) string {
|
||||||
dbClient, err := database.NewClient(config)
|
dbClient, err := database.NewClient(config)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return "", fmt.Errorf("unable to create new database client: %s", err)
|
|
||||||
}
|
|
||||||
machines, err := dbClient.ListMachines()
|
machines, err := dbClient.ListMachines()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return "", fmt.Errorf("Unable to list machines: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, machine := range machines {
|
for _, machine := range machines {
|
||||||
if machine.MachineId == machineID {
|
if machine.MachineId == machineID {
|
||||||
return machine.IpAddress, nil
|
return machine.IpAddress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAlertReaderFromFile(path string) *strings.Reader {
|
func GetAlertReaderFromFile(t *testing.T, path string) *strings.Reader {
|
||||||
alertContentBytes, err := os.ReadFile(path)
|
alertContentBytes, err := os.ReadFile(path)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
alerts := make([]*models.Alert, 0)
|
alerts := make([]*models.Alert, 0)
|
||||||
if err = json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
err = json.Unmarshal(alertContentBytes, &alerts)
|
||||||
log.Fatal(err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
for _, alert := range alerts {
|
for _, alert := range alerts {
|
||||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||||
|
@ -217,74 +198,57 @@ func GetAlertReaderFromFile(path string) *strings.Reader {
|
||||||
}
|
}
|
||||||
|
|
||||||
alertContent, err := json.Marshal(alerts)
|
alertContent, err := json.Marshal(alerts)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.NewReader(string(alertContent))
|
return strings.NewReader(string(alertContent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDecisionsGetResp(resp *httptest.ResponseRecorder) ([]*models.Decision, int, error) {
|
func readDecisionsGetResp(t *testing.T, resp *httptest.ResponseRecorder) ([]*models.Decision, int) {
|
||||||
var response []*models.Decision
|
var response []*models.Decision
|
||||||
|
|
||||||
if resp == nil {
|
require.NotNil(t, resp)
|
||||||
return nil, 0, errors.New("response is nil")
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp.Code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, resp.Code, nil
|
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return response, resp.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDecisionsErrorResp(resp *httptest.ResponseRecorder) (map[string]string, int, error) {
|
func readDecisionsErrorResp(t *testing.T, resp *httptest.ResponseRecorder) (map[string]string, int) {
|
||||||
var response map[string]string
|
var response map[string]string
|
||||||
|
|
||||||
if resp == nil {
|
require.NotNil(t, resp)
|
||||||
return nil, 0, errors.New("response is nil")
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp.Code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, resp.Code, nil
|
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return response, resp.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDecisionsDeleteResp(resp *httptest.ResponseRecorder) (*models.DeleteDecisionResponse, int, error) {
|
func readDecisionsDeleteResp(t *testing.T, resp *httptest.ResponseRecorder) (*models.DeleteDecisionResponse, int) {
|
||||||
var response models.DeleteDecisionResponse
|
var response models.DeleteDecisionResponse
|
||||||
|
|
||||||
if resp == nil {
|
require.NotNil(t, resp)
|
||||||
return nil, 0, errors.New("response is nil")
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, resp.Code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response, resp.Code, nil
|
return &response, resp.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDecisionsStreamResp(resp *httptest.ResponseRecorder) (map[string][]*models.Decision, int, error) {
|
func readDecisionsStreamResp(t *testing.T, resp *httptest.ResponseRecorder) (map[string][]*models.Decision, int) {
|
||||||
response := make(map[string][]*models.Decision)
|
response := make(map[string][]*models.Decision)
|
||||||
|
|
||||||
if resp == nil {
|
require.NotNil(t, resp)
|
||||||
return nil, 0, errors.New("response is nil")
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, resp.Code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, resp.Code, nil
|
return response, resp.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTestMachine(router *gin.Engine) (string, error) {
|
func CreateTestMachine(t *testing.T, router *gin.Engine) string {
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return "", fmt.Errorf("unable to marshal MachineTest")
|
|
||||||
}
|
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -292,26 +256,20 @@ func CreateTestMachine(router *gin.Engine) (string, error) {
|
||||||
req.Header.Set("User-Agent", UserAgent)
|
req.Header.Set("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
return body, nil
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTestBouncer(config *csconfig.DatabaseCfg) (string, error) {
|
func CreateTestBouncer(t *testing.T, config *csconfig.DatabaseCfg) string {
|
||||||
dbClient, err := database.NewClient(config)
|
dbClient, err := database.NewClient(config)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatalf("unable to create new database client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiKey, err := middlewares.GenerateAPIKey(keyLength)
|
apiKey, err := middlewares.GenerateAPIKey(keyLength)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return "", fmt.Errorf("unable to generate api key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dbClient.CreateBouncer("test", "127.0.0.1", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType)
|
_, err = dbClient.CreateBouncer("test", "127.0.0.1", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return "", fmt.Errorf("unable to create blocker: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiKey, nil
|
return apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithWrongDBConfig(t *testing.T) {
|
func TestWithWrongDBConfig(t *testing.T) {
|
||||||
|
@ -334,10 +292,7 @@ func TestWithWrongFlushConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnknownPath(t *testing.T) {
|
func TestUnknownPath(t *testing.T) {
|
||||||
router, _, err := NewAPITest(t)
|
router, _ := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||||
|
@ -384,24 +339,17 @@ func TestLoggingDebugToFileConfig(t *testing.T) {
|
||||||
LogDir: tempDir,
|
LogDir: tempDir,
|
||||||
DbConfig: &dbconfig,
|
DbConfig: &dbconfig,
|
||||||
}
|
}
|
||||||
lvl := log.DebugLevel
|
|
||||||
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
|
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
|
||||||
expectedLines := []string{"/test42"}
|
expectedLines := []string{"/test42"}
|
||||||
cfg.LogLevel = &lvl
|
cfg.LogLevel = ptr.Of(log.DebugLevel)
|
||||||
|
|
||||||
// Configure logging
|
// Configure logging
|
||||||
if err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false); err != nil {
|
err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false)
|
||||||
t.Fatal(err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
api, err := NewServer(&cfg)
|
api, err := NewServer(&cfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to create api : %s", err)
|
require.NotNil(t, api)
|
||||||
}
|
|
||||||
|
|
||||||
if api == nil {
|
|
||||||
t.Fatalf("failed to create api #2 is nbill")
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
|
||||||
|
@ -413,14 +361,10 @@ func TestLoggingDebugToFileConfig(t *testing.T) {
|
||||||
|
|
||||||
//check file content
|
//check file content
|
||||||
data, err := os.ReadFile(expectedFile)
|
data, err := os.ReadFile(expectedFile)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to read file : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, expectedStr := range expectedLines {
|
for _, expectedStr := range expectedLines {
|
||||||
if !strings.Contains(string(data), expectedStr) {
|
assert.Contains(t, string(data), expectedStr)
|
||||||
t.Fatalf("expected %s in %s", expectedStr, string(data))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,35 +390,29 @@ func TestLoggingErrorToFileConfig(t *testing.T) {
|
||||||
LogDir: tempDir,
|
LogDir: tempDir,
|
||||||
DbConfig: &dbconfig,
|
DbConfig: &dbconfig,
|
||||||
}
|
}
|
||||||
lvl := log.ErrorLevel
|
|
||||||
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
|
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
|
||||||
cfg.LogLevel = &lvl
|
cfg.LogLevel = ptr.Of(log.ErrorLevel)
|
||||||
|
|
||||||
// Configure logging
|
// Configure logging
|
||||||
if err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false); err != nil {
|
err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false)
|
||||||
t.Fatal(err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
api, err := NewServer(&cfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create api : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if api == nil {
|
api, err := NewServer(&cfg)
|
||||||
t.Fatalf("failed to create api #2 is nbill")
|
require.NoError(t, err)
|
||||||
}
|
require.NotNil(t, api)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
|
||||||
req.Header.Set("User-Agent", UserAgent)
|
req.Header.Set("User-Agent", UserAgent)
|
||||||
api.router.ServeHTTP(w, req)
|
api.router.ServeHTTP(w, req)
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
//wait for the request to happen
|
//wait for the request to happen
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
//check file content
|
//check file content
|
||||||
x, err := os.ReadFile(expectedFile)
|
x, err := os.ReadFile(expectedFile)
|
||||||
if err == nil && len(x) > 0 {
|
if err == nil {
|
||||||
t.Fatalf("file should be empty, got '%s'", x)
|
require.Empty(t, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove("./crowdsec.log")
|
os.Remove("./crowdsec.log")
|
||||||
|
|
|
@ -55,6 +55,7 @@ func serveHealth() http.HandlerFunc {
|
||||||
// no caching required
|
// no caching required
|
||||||
health.WithDisabledCache(),
|
health.WithDisabledCache(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return health.NewHandler(checker)
|
return health.NewHandler(checker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +77,7 @@ func (c *Controller) NewV1() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Router.GET("/health", gin.WrapF(serveHealth()))
|
c.Router.GET("/health", gin.WrapF(serveHealth()))
|
||||||
c.Router.Use(v1.PrometheusMiddleware())
|
c.Router.Use(v1.PrometheusMiddleware())
|
||||||
c.Router.HandleMethodNotAllowed = true
|
c.Router.HandleMethodNotAllowed = true
|
||||||
|
@ -104,7 +106,6 @@ func (c *Controller) NewV1() error {
|
||||||
jwtAuth.DELETE("/decisions", c.HandlerV1.DeleteDecisions)
|
jwtAuth.DELETE("/decisions", c.HandlerV1.DeleteDecisions)
|
||||||
jwtAuth.DELETE("/decisions/:decision_id", c.HandlerV1.DeleteDecisionById)
|
jwtAuth.DELETE("/decisions/:decision_id", c.HandlerV1.DeleteDecisionById)
|
||||||
jwtAuth.GET("/heartbeat", c.HandlerV1.HeartBeat)
|
jwtAuth.GET("/heartbeat", c.HandlerV1.HeartBeat)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKeyAuth := groupV1.Group("")
|
apiKeyAuth := groupV1.Group("")
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func FormatOneAlert(alert *ent.Alert) *models.Alert {
|
func FormatOneAlert(alert *ent.Alert) *models.Alert {
|
||||||
var outputAlert models.Alert
|
|
||||||
startAt := alert.StartedAt.String()
|
startAt := alert.StartedAt.String()
|
||||||
StopAt := alert.StoppedAt.String()
|
StopAt := alert.StoppedAt.String()
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ func FormatOneAlert(alert *ent.Alert) *models.Alert {
|
||||||
machineID = alert.Edges.Owner.MachineId
|
machineID = alert.Edges.Owner.MachineId
|
||||||
}
|
}
|
||||||
|
|
||||||
outputAlert = models.Alert{
|
outputAlert := models.Alert{
|
||||||
ID: int64(alert.ID),
|
ID: int64(alert.ID),
|
||||||
MachineID: machineID,
|
MachineID: machineID,
|
||||||
CreatedAt: alert.CreatedAt.Format(time.RFC3339),
|
CreatedAt: alert.CreatedAt.Format(time.RFC3339),
|
||||||
|
@ -58,23 +57,27 @@ func FormatOneAlert(alert *ent.Alert) *models.Alert {
|
||||||
Longitude: alert.SourceLongitude,
|
Longitude: alert.SourceLongitude,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, eventItem := range alert.Edges.Events {
|
for _, eventItem := range alert.Edges.Events {
|
||||||
var Metas models.Meta
|
var Metas models.Meta
|
||||||
timestamp := eventItem.Time.String()
|
timestamp := eventItem.Time.String()
|
||||||
if err := json.Unmarshal([]byte(eventItem.Serialized), &Metas); err != nil {
|
if err := json.Unmarshal([]byte(eventItem.Serialized), &Metas); err != nil {
|
||||||
log.Errorf("unable to unmarshall events meta '%s' : %s", eventItem.Serialized, err)
|
log.Errorf("unable to unmarshall events meta '%s' : %s", eventItem.Serialized, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputAlert.Events = append(outputAlert.Events, &models.Event{
|
outputAlert.Events = append(outputAlert.Events, &models.Event{
|
||||||
Timestamp: ×tamp,
|
Timestamp: ×tamp,
|
||||||
Meta: Metas,
|
Meta: Metas,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, metaItem := range alert.Edges.Metas {
|
for _, metaItem := range alert.Edges.Metas {
|
||||||
outputAlert.Meta = append(outputAlert.Meta, &models.MetaItems0{
|
outputAlert.Meta = append(outputAlert.Meta, &models.MetaItems0{
|
||||||
Key: metaItem.Key,
|
Key: metaItem.Key,
|
||||||
Value: metaItem.Value,
|
Value: metaItem.Value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, decisionItem := range alert.Edges.Decisions {
|
for _, decisionItem := range alert.Edges.Decisions {
|
||||||
duration := decisionItem.Until.Sub(time.Now().UTC()).String()
|
duration := decisionItem.Until.Sub(time.Now().UTC()).String()
|
||||||
outputAlert.Decisions = append(outputAlert.Decisions, &models.Decision{
|
outputAlert.Decisions = append(outputAlert.Decisions, &models.Decision{
|
||||||
|
@ -88,6 +91,7 @@ func FormatOneAlert(alert *ent.Alert) *models.Alert {
|
||||||
ID: int64(decisionItem.ID),
|
ID: int64(decisionItem.ID),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &outputAlert
|
return &outputAlert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +101,7 @@ func FormatAlerts(result []*ent.Alert) models.AddAlertsRequest {
|
||||||
for _, alertItem := range result {
|
for _, alertItem := range result {
|
||||||
data = append(data, FormatOneAlert(alertItem))
|
data = append(data, FormatOneAlert(alertItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +112,7 @@ func (c *Controller) sendAlertToPluginChannel(alert *models.Alert, profileID uin
|
||||||
select {
|
select {
|
||||||
case c.PluginChannel <- csplugin.ProfileAlert{ProfileID: profileID, Alert: alert}:
|
case c.PluginChannel <- csplugin.ProfileAlert{ProfileID: profileID, Alert: alert}:
|
||||||
log.Debugf("alert sent to Plugin channel")
|
log.Debugf("alert sent to Plugin channel")
|
||||||
|
|
||||||
break RETRY
|
break RETRY
|
||||||
default:
|
default:
|
||||||
log.Warningf("Cannot send alert to Plugin channel (try: %d)", try)
|
log.Warningf("Cannot send alert to Plugin channel (try: %d)", try)
|
||||||
|
@ -133,7 +139,6 @@ func normalizeScope(scope string) string {
|
||||||
|
|
||||||
// CreateAlert writes the alerts received in the body to the database
|
// CreateAlert writes the alerts received in the body to the database
|
||||||
func (c *Controller) CreateAlert(gctx *gin.Context) {
|
func (c *Controller) CreateAlert(gctx *gin.Context) {
|
||||||
|
|
||||||
var input models.AddAlertsRequest
|
var input models.AddAlertsRequest
|
||||||
|
|
||||||
claims := jwt.ExtractClaims(gctx)
|
claims := jwt.ExtractClaims(gctx)
|
||||||
|
@ -144,13 +149,16 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
|
||||||
gctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
|
gctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := input.Validate(strfmt.Default); err != nil {
|
if err := input.Validate(strfmt.Default); err != nil {
|
||||||
c.HandleDBErrors(gctx, err)
|
c.HandleDBErrors(gctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stopFlush := false
|
stopFlush := false
|
||||||
|
|
||||||
for _, alert := range input {
|
for _, alert := range input {
|
||||||
//normalize scope for alert.Source and decisions
|
// normalize scope for alert.Source and decisions
|
||||||
if alert.Source.Scope != nil {
|
if alert.Source.Scope != nil {
|
||||||
*alert.Source.Scope = normalizeScope(*alert.Source.Scope)
|
*alert.Source.Scope = normalizeScope(*alert.Source.Scope)
|
||||||
}
|
}
|
||||||
|
@ -161,15 +169,16 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
alert.MachineID = machineID
|
alert.MachineID = machineID
|
||||||
//generate uuid here for alert
|
// generate uuid here for alert
|
||||||
alert.UUID = uuid.NewString()
|
alert.UUID = uuid.NewString()
|
||||||
|
|
||||||
//if coming from cscli, alert already has decisions
|
// if coming from cscli, alert already has decisions
|
||||||
if len(alert.Decisions) != 0 {
|
if len(alert.Decisions) != 0 {
|
||||||
//alert already has a decision (cscli decisions add etc.), generate uuid here
|
//alert already has a decision (cscli decisions add etc.), generate uuid here
|
||||||
for _, decision := range alert.Decisions {
|
for _, decision := range alert.Decisions {
|
||||||
decision.UUID = uuid.NewString()
|
decision.UUID = uuid.NewString()
|
||||||
}
|
}
|
||||||
|
|
||||||
for pIdx, profile := range c.Profiles {
|
for pIdx, profile := range c.Profiles {
|
||||||
_, matched, err := profile.EvaluateProfile(alert)
|
_, matched, err := profile.EvaluateProfile(alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -16,23 +15,22 @@ func TestDeleteDecisionRange(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_minibulk.json")
|
||||||
|
|
||||||
// delete by ip wrong
|
// delete by ip wrong
|
||||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?range=1.2.3.0/24", emptyBody, PASSWORD)
|
w := lapi.RecordResponse(t, "DELETE", "/v1/decisions?range=1.2.3.0/24", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||||
|
|
||||||
// delete by range
|
// delete by range
|
||||||
|
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
||||||
|
|
||||||
// delete by range : ensure it was already deleted
|
// delete by range : ensure it was already deleted
|
||||||
|
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?range=91.121.79.0/24", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions?range=91.121.79.0/24", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -41,23 +39,23 @@ func TestDeleteDecisionFilter(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_minibulk.json")
|
||||||
|
|
||||||
// delete by ip wrong
|
// delete by ip wrong
|
||||||
|
|
||||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?ip=1.2.3.4", emptyBody, PASSWORD)
|
w := lapi.RecordResponse(t, "DELETE", "/v1/decisions?ip=1.2.3.4", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||||
|
|
||||||
// delete by ip good
|
// delete by ip good
|
||||||
|
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?ip=91.121.79.179", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions?ip=91.121.79.179", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
|
|
||||||
// delete by scope/value
|
// delete by scope/value
|
||||||
|
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?scopes=Ip&value=91.121.79.178", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions?scopes=Ip&value=91.121.79.178", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -66,17 +64,17 @@ func TestDeleteDecisionFilterByScenario(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_minibulk.json")
|
||||||
|
|
||||||
// delete by wrong scenario
|
// delete by wrong scenario
|
||||||
|
|
||||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bff", emptyBody, PASSWORD)
|
w := lapi.RecordResponse(t, "DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bff", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||||
|
|
||||||
// delete by scenario good
|
// delete by scenario good
|
||||||
|
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bf", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bf", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -85,14 +83,13 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_minibulk.json")
|
||||||
|
|
||||||
// Get Decision
|
// Get Decision
|
||||||
|
|
||||||
w := lapi.RecordResponse("GET", "/v1/decisions", emptyBody, APIKEY)
|
w := lapi.RecordResponse(t, "GET", "/v1/decisions", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err := readDecisionsGetResp(w)
|
decisions, code := readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 2)
|
assert.Len(t, decisions, 2)
|
||||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||||
|
@ -104,10 +101,9 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
|
|
||||||
// Get Decision : type filter
|
// Get Decision : type filter
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions?type=ban", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions?type=ban", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err = readDecisionsGetResp(w)
|
decisions, code = readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 2)
|
assert.Len(t, decisions, 2)
|
||||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||||
|
@ -122,10 +118,9 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
|
|
||||||
// Get Decision : scope/value
|
// Get Decision : scope/value
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions?scopes=Ip&value=91.121.79.179", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions?scopes=Ip&value=91.121.79.179", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err = readDecisionsGetResp(w)
|
decisions, code = readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 1)
|
assert.Len(t, decisions, 1)
|
||||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||||
|
@ -137,10 +132,9 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
|
|
||||||
// Get Decision : ip filter
|
// Get Decision : ip filter
|
||||||
|
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions?ip=91.121.79.179", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions?ip=91.121.79.179", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err = readDecisionsGetResp(w)
|
decisions, code = readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 1)
|
assert.Len(t, decisions, 1)
|
||||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||||
|
@ -151,10 +145,9 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
// assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
// assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||||
|
|
||||||
// Get decision : by range
|
// Get decision : by range
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err = readDecisionsGetResp(w)
|
decisions, code = readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 2)
|
assert.Len(t, decisions, 2)
|
||||||
assert.Contains(t, []string{*decisions[0].Value, *decisions[1].Value}, "91.121.79.179")
|
assert.Contains(t, []string{*decisions[0].Value, *decisions[1].Value}, "91.121.79.179")
|
||||||
|
@ -165,13 +158,12 @@ func TestGetDecision(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
// Get Decision
|
// Get Decision
|
||||||
w := lapi.RecordResponse("GET", "/v1/decisions", emptyBody, APIKEY)
|
w := lapi.RecordResponse(t, "GET", "/v1/decisions", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
decisions, code, err := readDecisionsGetResp(w)
|
decisions, code := readDecisionsGetResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions, 3)
|
assert.Len(t, decisions, 3)
|
||||||
/*decisions get doesn't perform deduplication*/
|
/*decisions get doesn't perform deduplication*/
|
||||||
|
@ -188,7 +180,7 @@ func TestGetDecision(t *testing.T) {
|
||||||
assert.Equal(t, int64(3), decisions[2].ID)
|
assert.Equal(t, int64(3), decisions[2].ID)
|
||||||
|
|
||||||
// Get Decision with invalid filter. It should ignore this filter
|
// Get Decision with invalid filter. It should ignore this filter
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions?test=test", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions?test=test", emptyBody, APIKEY)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Len(t, decisions, 3)
|
assert.Len(t, decisions, 3)
|
||||||
}
|
}
|
||||||
|
@ -197,49 +189,43 @@ func TestDeleteDecisionByID(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
//Have one alerts
|
//Have one alerts
|
||||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w := lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err := readDecisionsStreamResp(w)
|
decisions, code := readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
|
||||||
// Delete alert with Invalid ID
|
// Delete alert with Invalid ID
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/test", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/test", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, 400, w.Code)
|
||||||
errResp, _, err := readDecisionsErrorResp(w)
|
errResp, _ := readDecisionsErrorResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "decision_id must be valid integer", errResp["message"])
|
assert.Equal(t, "decision_id must be valid integer", errResp["message"])
|
||||||
|
|
||||||
// Delete alert with ID that not exist
|
// Delete alert with ID that not exist
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/100", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/100", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
errResp, _, err = readDecisionsErrorResp(w)
|
errResp, _ = readDecisionsErrorResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "decision with id '100' doesn't exist: unable to delete", errResp["message"])
|
assert.Equal(t, "decision with id '100' doesn't exist: unable to delete", errResp["message"])
|
||||||
|
|
||||||
//Have one alerts
|
//Have one alerts
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err = readDecisionsStreamResp(w)
|
decisions, code = readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
|
||||||
// Delete alert with valid ID
|
// Delete alert with valid ID
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/1", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/1", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
resp, _, err := readDecisionsDeleteResp(w)
|
resp, _ := readDecisionsDeleteResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "1", resp.NbDeleted)
|
assert.Equal(t, "1", resp.NbDeleted)
|
||||||
|
|
||||||
//Have one alert (because we delete an alert that has dup targets)
|
//Have one alert (because we delete an alert that has dup targets)
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err = readDecisionsStreamResp(w)
|
decisions, code = readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
@ -249,20 +235,18 @@ func TestDeleteDecision(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
// Delete alert with Invalid filter
|
// Delete alert with Invalid filter
|
||||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?test=test", emptyBody, PASSWORD)
|
w := lapi.RecordResponse(t, "DELETE", "/v1/decisions?test=test", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
errResp, _, err := readDecisionsErrorResp(w)
|
errResp, _ := readDecisionsErrorResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "'test' doesn't exist: invalid filter", errResp["message"])
|
assert.Equal(t, "'test' doesn't exist: invalid filter", errResp["message"])
|
||||||
|
|
||||||
// Delete all alert
|
// Delete all alert
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
resp, _, err := readDecisionsDeleteResp(w)
|
resp, _ := readDecisionsDeleteResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "3", resp.NbDeleted)
|
assert.Equal(t, "3", resp.NbDeleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,12 +255,11 @@ func TestStreamStartDecisionDedup(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
// Create Valid Alert : 3 decisions for 127.0.0.1, longest has id=3
|
// Create Valid Alert : 3 decisions for 127.0.0.1, longest has id=3
|
||||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
lapi.InsertAlertFromFile(t, "./tests/alert_sample.json")
|
||||||
|
|
||||||
// Get Stream, we only get one decision (the longest one)
|
// Get Stream, we only get one decision (the longest one)
|
||||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w := lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err := readDecisionsStreamResp(w)
|
decisions, code := readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
@ -285,13 +268,12 @@ func TestStreamStartDecisionDedup(t *testing.T) {
|
||||||
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
||||||
|
|
||||||
// id=3 decision is deleted, this won't affect `deleted`, because there are decisions on the same ip
|
// id=3 decision is deleted, this won't affect `deleted`, because there are decisions on the same ip
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/3", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/3", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
// Get Stream, we only get one decision (the longest one, id=2)
|
// Get Stream, we only get one decision (the longest one, id=2)
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err = readDecisionsStreamResp(w)
|
decisions, code = readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
@ -300,13 +282,12 @@ func TestStreamStartDecisionDedup(t *testing.T) {
|
||||||
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
||||||
|
|
||||||
// We delete another decision, yet don't receive it in stream, since there's another decision on same IP
|
// We delete another decision, yet don't receive it in stream, since there's another decision on same IP
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/2", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/2", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
// And get the remaining decision (1)
|
// And get the remaining decision (1)
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err = readDecisionsStreamResp(w)
|
decisions, code = readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Empty(t, decisions["deleted"])
|
assert.Empty(t, decisions["deleted"])
|
||||||
assert.Len(t, decisions["new"], 1)
|
assert.Len(t, decisions["new"], 1)
|
||||||
|
@ -315,13 +296,12 @@ func TestStreamStartDecisionDedup(t *testing.T) {
|
||||||
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
assert.Equal(t, "127.0.0.1", *decisions["new"][0].Value)
|
||||||
|
|
||||||
// We delete the last decision, we receive the delete order
|
// We delete the last decision, we receive the delete order
|
||||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/1", emptyBody, PASSWORD)
|
w = lapi.RecordResponse(t, "DELETE", "/v1/decisions/1", emptyBody, PASSWORD)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
//and now we only get a deleted decision
|
//and now we only get a deleted decision
|
||||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
w = lapi.RecordResponse(t, "GET", "/v1/decisions/stream?startup=true", emptyBody, APIKEY)
|
||||||
decisions, code, err = readDecisionsStreamResp(w)
|
decisions, code = readDecisionsStreamResp(t, w)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 200, code)
|
assert.Equal(t, 200, code)
|
||||||
assert.Len(t, decisions["deleted"], 1)
|
assert.Len(t, decisions["deleted"], 1)
|
||||||
assert.Equal(t, int64(1), decisions["deleted"][0].ID)
|
assert.Equal(t, int64(1), decisions["deleted"][0].ID)
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
func TestHeartBeat(t *testing.T) {
|
func TestHeartBeat(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
w := lapi.RecordResponse(http.MethodGet, "/v1/heartbeat", emptyBody, "password")
|
w := lapi.RecordResponse(t, http.MethodGet, "/v1/heartbeat", emptyBody, "password")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
w = lapi.RecordResponse("POST", "/v1/heartbeat", emptyBody, "password")
|
w = lapi.RecordResponse(t, "POST", "/v1/heartbeat", emptyBody, "password")
|
||||||
assert.Equal(t, 405, w.Code)
|
assert.Equal(t, 405, w.Code)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
func TestLogin(t *testing.T) {
|
||||||
router, config, err := NewAPITest(t)
|
router, config := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := CreateTestMachine(router)
|
body := CreateTestMachine(t, router)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login with machine not validated yet
|
// Login with machine not validated yet
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -28,16 +21,16 @@ func TestLogin(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, "{\"code\":401,\"message\":\"machine test not validated\"}", w.Body.String())
|
assert.Equal(t, `{"code":401,"message":"machine test not validated"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with machine not exist
|
// Login with machine not exist
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader("{\"machine_id\": \"test1\", \"password\": \"test1\"}"))
|
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(`{"machine_id": "test1", "password": "test1"}`))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, "{\"code\":401,\"message\":\"ent: machine not found\"}", w.Body.String())
|
assert.Equal(t, `{"code":401,"message":"ent: machine not found"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with invalid body
|
// Login with invalid body
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
|
@ -46,31 +39,28 @@ func TestLogin(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, "{\"code\":401,\"message\":\"missing: invalid character 'e' in literal true (expecting 'r')\"}", w.Body.String())
|
assert.Equal(t, `{"code":401,"message":"missing: invalid character 'e' in literal true (expecting 'r')"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with invalid format
|
// Login with invalid format
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader("{\"machine_id\": \"test1\"}"))
|
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(`{"machine_id": "test1"}`))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, "{\"code\":401,\"message\":\"validation failure list:\\npassword in body is required\"}", w.Body.String())
|
assert.Equal(t, `{"code":401,"message":"validation failure list:\npassword in body is required"}`, w.Body.String())
|
||||||
|
|
||||||
//Validate machine
|
//Validate machine
|
||||||
err = ValidateMachine("test", config.API.Server.DbConfig)
|
ValidateMachine(t, "test", config.API.Server.DbConfig)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login with invalid password
|
// Login with invalid password
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader("{\"machine_id\": \"test\", \"password\": \"test1\"}"))
|
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(`{"machine_id": "test", "password": "test1"}`))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, "{\"code\":401,\"message\":\"incorrect Username or Password\"}", w.Body.String())
|
assert.Equal(t, `{"code":401,"message":"incorrect Username or Password"}`, w.Body.String())
|
||||||
|
|
||||||
// Login with valid machine
|
// Login with valid machine
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
|
@ -79,16 +69,16 @@ func TestLogin(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "\"token\"")
|
assert.Contains(t, w.Body.String(), `"token"`)
|
||||||
assert.Contains(t, w.Body.String(), "\"expire\"")
|
assert.Contains(t, w.Body.String(), `"expire"`)
|
||||||
|
|
||||||
// Login with valid machine + scenarios
|
// Login with valid machine + scenarios
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader("{\"machine_id\": \"test\", \"password\": \"test\", \"scenarios\": [\"crowdsecurity/test\", \"crowdsecurity/test2\"]}"))
|
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers/login", strings.NewReader(`{"machine_id": "test", "password": "test", "scenarios": ["crowdsecurity/test", "crowdsecurity/test2"]}`))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "\"token\"")
|
assert.Contains(t, w.Body.String(), `"token"`)
|
||||||
assert.Contains(t, w.Body.String(), "\"expire\"")
|
assert.Contains(t, w.Body.String(), `"expire"`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateMachine(t *testing.T) {
|
func TestCreateMachine(t *testing.T) {
|
||||||
router, _, err := NewAPITest(t)
|
router, _ := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create machine with invalid format
|
// Create machine with invalid format
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -24,22 +21,21 @@ func TestCreateMachine(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"invalid character 'e' in literal true (expecting 'r')\"}", w.Body.String())
|
assert.Equal(t, `{"message":"invalid character 'e' in literal true (expecting 'r')"}`, w.Body.String())
|
||||||
|
|
||||||
// Create machine with invalid input
|
// Create machine with invalid input
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader("{\"test\": \"test\"}"))
|
req, _ = http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(`{"test": "test"}`))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"validation failure list:\\nmachine_id in body is required\\npassword in body is required\"}", w.Body.String())
|
assert.Equal(t, `{"message":"validation failure list:\nmachine_id in body is required\npassword in body is required"}`, w.Body.String())
|
||||||
|
|
||||||
// Create machine
|
// Create machine
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal("unable to marshal MachineTest")
|
|
||||||
}
|
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
|
@ -52,16 +48,12 @@ func TestCreateMachine(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateMachineWithForwardedFor(t *testing.T) {
|
func TestCreateMachineWithForwardedFor(t *testing.T) {
|
||||||
router, config, err := NewAPITestForwardedFor(t)
|
router, config := NewAPITestForwardedFor(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
router.TrustedPlatform = "X-Real-IP"
|
router.TrustedPlatform = "X-Real-IP"
|
||||||
// Create machine
|
// Create machine
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal("unable to marshal MachineTest")
|
|
||||||
}
|
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -73,25 +65,18 @@ func TestCreateMachineWithForwardedFor(t *testing.T) {
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
ip, err := GetMachineIP(*MachineTest.MachineID, config.API.Server.DbConfig)
|
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get machine IP : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "1.1.1.1", ip)
|
assert.Equal(t, "1.1.1.1", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateMachineWithForwardedForNoConfig(t *testing.T) {
|
func TestCreateMachineWithForwardedForNoConfig(t *testing.T) {
|
||||||
router, config, err := NewAPITest(t)
|
router, config := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create machine
|
// Create machine
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal("unable to marshal MachineTest")
|
|
||||||
}
|
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -103,26 +88,20 @@ func TestCreateMachineWithForwardedForNoConfig(t *testing.T) {
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
ip, err := GetMachineIP(*MachineTest.MachineID, config.API.Server.DbConfig)
|
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get machine IP : %s", err)
|
|
||||||
}
|
|
||||||
//For some reason, the IP is empty when running tests
|
//For some reason, the IP is empty when running tests
|
||||||
//if no forwarded-for headers are present
|
//if no forwarded-for headers are present
|
||||||
assert.Equal(t, "", ip)
|
assert.Equal(t, "", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
||||||
router, config, err := NewAPITestForwardedFor(t)
|
router, config := NewAPITestForwardedFor(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create machine
|
// Create machine
|
||||||
b, err := json.Marshal(MachineTest)
|
b, err := json.Marshal(MachineTest)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
log.Fatal("unable to marshal MachineTest")
|
|
||||||
}
|
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -133,25 +112,17 @@ func TestCreateMachineWithoutForwardedFor(t *testing.T) {
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
ip, err := GetMachineIP(*MachineTest.MachineID, config.API.Server.DbConfig)
|
ip := GetMachineIP(t, *MachineTest.MachineID, config.API.Server.DbConfig)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not get machine IP : %s", err)
|
|
||||||
}
|
|
||||||
//For some reason, the IP is empty when running tests
|
//For some reason, the IP is empty when running tests
|
||||||
//if no forwarded-for headers are present
|
//if no forwarded-for headers are present
|
||||||
assert.Equal(t, "", ip)
|
assert.Equal(t, "", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateMachineAlreadyExist(t *testing.T) {
|
func TestCreateMachineAlreadyExist(t *testing.T) {
|
||||||
router, _, err := NewAPITest(t)
|
router, _ := NewAPITest(t)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to run local API: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := CreateTestMachine(router)
|
body := CreateTestMachine(t, router)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
|
||||||
|
@ -164,5 +135,5 @@ func TestCreateMachineAlreadyExist(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 403, w.Code)
|
assert.Equal(t, 403, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"user 'test': user already exist\"}", w.Body.String())
|
assert.Equal(t, `{"message":"user 'test': user already exist"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ rules:
|
||||||
type match struct {
|
type match struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Value string `yaml:"value"`
|
Value string `yaml:"value"`
|
||||||
|
Not bool `yaml:"not,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomRule struct {
|
type CustomRule struct {
|
||||||
|
@ -40,7 +41,8 @@ type CustomRule struct {
|
||||||
Transform []string `yaml:"transform"` //t:lowercase, t:uppercase, etc
|
Transform []string `yaml:"transform"` //t:lowercase, t:uppercase, etc
|
||||||
And []CustomRule `yaml:"and,omitempty"`
|
And []CustomRule `yaml:"and,omitempty"`
|
||||||
Or []CustomRule `yaml:"or,omitempty"`
|
Or []CustomRule `yaml:"or,omitempty"`
|
||||||
BodyType string `yaml:"body_type,omitempty"`
|
|
||||||
|
BodyType string `yaml:"body_type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *CustomRule) Convert(ruleType string, appsecRuleName string) (string, []uint32, error) {
|
func (v *CustomRule) Convert(ruleType string, appsecRuleName string) (string, []uint32, error) {
|
||||||
|
|
|
@ -8,6 +8,16 @@ func TestVPatchRuleString(t *testing.T) {
|
||||||
rule CustomRule
|
rule CustomRule
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "Collection count",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"ARGS"},
|
||||||
|
Variables: []string{"foo"},
|
||||||
|
Match: match{Type: "eq", Value: "1"},
|
||||||
|
Transform: []string{"count"},
|
||||||
|
},
|
||||||
|
expected: `SecRule &ARGS_GET:foo "@eq 1" "id:853070236,phase:2,deny,log,msg:'Collection count',tag:'crowdsec-Collection count'"`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Base Rule",
|
name: "Base Rule",
|
||||||
rule: CustomRule{
|
rule: CustomRule{
|
||||||
|
@ -18,6 +28,32 @@ func TestVPatchRuleString(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2203944045,phase:2,deny,log,msg:'Base Rule',tag:'crowdsec-Base Rule',t:lowercase"`,
|
expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2203944045,phase:2,deny,log,msg:'Base Rule',tag:'crowdsec-Base Rule',t:lowercase"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "One zone, multi var",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"ARGS"},
|
||||||
|
Variables: []string{"foo", "bar"},
|
||||||
|
Match: match{Type: "regex", Value: "[^a-zA-Z]"},
|
||||||
|
Transform: []string{"lowercase"},
|
||||||
|
},
|
||||||
|
expected: `SecRule ARGS_GET:foo|ARGS_GET:bar "@rx [^a-zA-Z]" "id:385719930,phase:2,deny,log,msg:'One zone, multi var',tag:'crowdsec-One zone, multi var',t:lowercase"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Base Rule #2",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"METHOD"},
|
||||||
|
Match: match{Type: "startsWith", Value: "toto"},
|
||||||
|
},
|
||||||
|
expected: `SecRule REQUEST_METHOD "@beginsWith toto" "id:2759779019,phase:2,deny,log,msg:'Base Rule #2',tag:'crowdsec-Base Rule #2'"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Base Negative Rule",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"METHOD"},
|
||||||
|
Match: match{Type: "startsWith", Value: "toto", Not: true},
|
||||||
|
},
|
||||||
|
expected: `SecRule REQUEST_METHOD "!@beginsWith toto" "id:3966251995,phase:2,deny,log,msg:'Base Negative Rule',tag:'crowdsec-Base Negative Rule'"`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Multiple Zones",
|
name: "Multiple Zones",
|
||||||
rule: CustomRule{
|
rule: CustomRule{
|
||||||
|
@ -28,6 +64,25 @@ func TestVPatchRuleString(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: `SecRule ARGS_GET:foo|ARGS_POST:foo "@rx [^a-zA-Z]" "id:3387135861,phase:2,deny,log,msg:'Multiple Zones',tag:'crowdsec-Multiple Zones',t:lowercase"`,
|
expected: `SecRule ARGS_GET:foo|ARGS_POST:foo "@rx [^a-zA-Z]" "id:3387135861,phase:2,deny,log,msg:'Multiple Zones',tag:'crowdsec-Multiple Zones',t:lowercase"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple Zones Multi Var",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"ARGS", "BODY_ARGS"},
|
||||||
|
Variables: []string{"foo", "bar"},
|
||||||
|
Match: match{Type: "regex", Value: "[^a-zA-Z]"},
|
||||||
|
Transform: []string{"lowercase"},
|
||||||
|
},
|
||||||
|
expected: `SecRule ARGS_GET:foo|ARGS_GET:bar|ARGS_POST:foo|ARGS_POST:bar "@rx [^a-zA-Z]" "id:1119773585,phase:2,deny,log,msg:'Multiple Zones Multi Var',tag:'crowdsec-Multiple Zones Multi Var',t:lowercase"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple Zones No Vars",
|
||||||
|
rule: CustomRule{
|
||||||
|
Zones: []string{"ARGS", "BODY_ARGS"},
|
||||||
|
Match: match{Type: "regex", Value: "[^a-zA-Z]"},
|
||||||
|
Transform: []string{"lowercase"},
|
||||||
|
},
|
||||||
|
expected: `SecRule ARGS_GET|ARGS_POST "@rx [^a-zA-Z]" "id:2020110336,phase:2,deny,log,msg:'Multiple Zones No Vars',tag:'crowdsec-Multiple Zones No Vars',t:lowercase"`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Basic AND",
|
name: "Basic AND",
|
||||||
rule: CustomRule{
|
rule: CustomRule{
|
||||||
|
|
|
@ -44,6 +44,7 @@ var matchMap map[string]string = map[string]string{
|
||||||
"lt": "@lt",
|
"lt": "@lt",
|
||||||
"gte": "@ge",
|
"gte": "@ge",
|
||||||
"lte": "@le",
|
"lte": "@le",
|
||||||
|
"eq": "@eq",
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyTypeMatch map[string]string = map[string]string{
|
var bodyTypeMatch map[string]string = map[string]string{
|
||||||
|
@ -121,7 +122,20 @@ func (m *ModsecurityRule) buildRules(rule *CustomRule, appsecRuleName string, an
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zone_prefix := ""
|
||||||
|
variable_prefix := ""
|
||||||
|
if rule.Transform != nil {
|
||||||
|
for tidx, transform := range rule.Transform {
|
||||||
|
if transform == "count" {
|
||||||
|
zone_prefix = "&"
|
||||||
|
rule.Transform[tidx] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for idx, zone := range rule.Zones {
|
for idx, zone := range rule.Zones {
|
||||||
|
if idx > 0 {
|
||||||
|
r.WriteByte('|')
|
||||||
|
}
|
||||||
mappedZone, ok := zonesMap[zone]
|
mappedZone, ok := zonesMap[zone]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unknown zone '%s'", zone)
|
return nil, fmt.Errorf("unknown zone '%s'", zone)
|
||||||
|
@ -130,10 +144,10 @@ func (m *ModsecurityRule) buildRules(rule *CustomRule, appsecRuleName string, an
|
||||||
r.WriteString(mappedZone)
|
r.WriteString(mappedZone)
|
||||||
} else {
|
} else {
|
||||||
for j, variable := range rule.Variables {
|
for j, variable := range rule.Variables {
|
||||||
if idx > 0 || j > 0 {
|
if j > 0 {
|
||||||
r.WriteByte('|')
|
r.WriteByte('|')
|
||||||
}
|
}
|
||||||
r.WriteString(fmt.Sprintf("%s:%s", mappedZone, variable))
|
r.WriteString(fmt.Sprintf("%s%s:%s%s", zone_prefix, mappedZone, variable_prefix, variable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +155,11 @@ func (m *ModsecurityRule) buildRules(rule *CustomRule, appsecRuleName string, an
|
||||||
|
|
||||||
if rule.Match.Type != "" {
|
if rule.Match.Type != "" {
|
||||||
if match, ok := matchMap[rule.Match.Type]; ok {
|
if match, ok := matchMap[rule.Match.Type]; ok {
|
||||||
r.WriteString(fmt.Sprintf(`"%s %s"`, match, rule.Match.Value))
|
prefix := ""
|
||||||
|
if rule.Match.Not {
|
||||||
|
prefix = "!"
|
||||||
|
}
|
||||||
|
r.WriteString(fmt.Sprintf(`"%s%s %s"`, prefix, match, rule.Match.Value))
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unknown match type '%s'", rule.Match.Type)
|
return nil, fmt.Errorf("unknown match type '%s'", rule.Match.Type)
|
||||||
}
|
}
|
||||||
|
@ -152,6 +170,9 @@ func (m *ModsecurityRule) buildRules(rule *CustomRule, appsecRuleName string, an
|
||||||
|
|
||||||
if rule.Transform != nil {
|
if rule.Transform != nil {
|
||||||
for _, transform := range rule.Transform {
|
for _, transform := range rule.Transform {
|
||||||
|
if transform == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
r.WriteByte(',')
|
r.WriteByte(',')
|
||||||
if mappedTransform, ok := transformMap[transform]; ok {
|
if mappedTransform, ok := transformMap[transform]; ok {
|
||||||
r.WriteString(mappedTransform)
|
r.WriteString(mappedTransform)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -267,7 +268,7 @@ func (r *ReqDumpFilter) ToJSON() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a ParsedRequest from a http.Request. ParsedRequest can be consumed by the App security Engine
|
// Generate a ParsedRequest from a http.Request. ParsedRequest can be consumed by the App security Engine
|
||||||
func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) {
|
func NewParsedRequestFromRequest(r *http.Request, logger *logrus.Entry) (ParsedRequest, error) {
|
||||||
var err error
|
var err error
|
||||||
contentLength := r.ContentLength
|
contentLength := r.ContentLength
|
||||||
if contentLength < 0 {
|
if contentLength < 0 {
|
||||||
|
@ -282,26 +283,23 @@ func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the real source of the request is set in 'x-client-ip'
|
|
||||||
clientIP := r.Header.Get(IPHeaderName)
|
clientIP := r.Header.Get(IPHeaderName)
|
||||||
if clientIP == "" {
|
if clientIP == "" {
|
||||||
return ParsedRequest{}, fmt.Errorf("missing '%s' header", IPHeaderName)
|
return ParsedRequest{}, fmt.Errorf("missing '%s' header", IPHeaderName)
|
||||||
}
|
}
|
||||||
// the real target Host of the request is set in 'x-client-host'
|
|
||||||
clientHost := r.Header.Get(HostHeaderName)
|
|
||||||
if clientHost == "" {
|
|
||||||
return ParsedRequest{}, fmt.Errorf("missing '%s' header", HostHeaderName)
|
|
||||||
}
|
|
||||||
// the real URI of the request is set in 'x-client-uri'
|
|
||||||
clientURI := r.Header.Get(URIHeaderName)
|
clientURI := r.Header.Get(URIHeaderName)
|
||||||
if clientURI == "" {
|
if clientURI == "" {
|
||||||
return ParsedRequest{}, fmt.Errorf("missing '%s' header", URIHeaderName)
|
return ParsedRequest{}, fmt.Errorf("missing '%s' header", URIHeaderName)
|
||||||
}
|
}
|
||||||
// the real VERB of the request is set in 'x-client-uri'
|
|
||||||
clientMethod := r.Header.Get(VerbHeaderName)
|
clientMethod := r.Header.Get(VerbHeaderName)
|
||||||
if clientMethod == "" {
|
if clientMethod == "" {
|
||||||
return ParsedRequest{}, fmt.Errorf("missing '%s' header", VerbHeaderName)
|
return ParsedRequest{}, fmt.Errorf("missing '%s' header", VerbHeaderName)
|
||||||
}
|
}
|
||||||
|
clientHost := r.Header.Get(HostHeaderName)
|
||||||
|
if clientHost == "" { //this might be empty
|
||||||
|
logger.Debugf("missing '%s' header", HostHeaderName)
|
||||||
|
}
|
||||||
|
|
||||||
// delete those headers before coraza process the request
|
// delete those headers before coraza process the request
|
||||||
delete(r.Header, IPHeaderName)
|
delete(r.Header, IPHeaderName)
|
||||||
|
|
|
@ -61,36 +61,51 @@ func (a *CTICfg) Load() error {
|
||||||
if a.Key == nil {
|
if a.Key == nil {
|
||||||
*a.Enabled = false
|
*a.Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Key != nil && *a.Key == "" {
|
if a.Key != nil && *a.Key == "" {
|
||||||
return fmt.Errorf("empty cti key")
|
return fmt.Errorf("empty cti key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Enabled == nil {
|
if a.Enabled == nil {
|
||||||
a.Enabled = new(bool)
|
a.Enabled = new(bool)
|
||||||
*a.Enabled = true
|
*a.Enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.CacheTimeout == nil {
|
if a.CacheTimeout == nil {
|
||||||
a.CacheTimeout = new(time.Duration)
|
a.CacheTimeout = new(time.Duration)
|
||||||
*a.CacheTimeout = 10 * time.Minute
|
*a.CacheTimeout = 10 * time.Minute
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.CacheSize == nil {
|
if a.CacheSize == nil {
|
||||||
a.CacheSize = new(int)
|
a.CacheSize = new(int)
|
||||||
*a.CacheSize = 100
|
*a.CacheSize = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OnlineApiClientCfg) Load() error {
|
func (o *OnlineApiClientCfg) Load() error {
|
||||||
o.Credentials = new(ApiCredentialsCfg)
|
o.Credentials = new(ApiCredentialsCfg)
|
||||||
|
|
||||||
fcontent, err := os.ReadFile(o.CredentialsFilePath)
|
fcontent, err := os.ReadFile(o.CredentialsFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read api server credentials configuration file '%s': %w", o.CredentialsFilePath, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.UnmarshalStrict(fcontent, o.Credentials)
|
err = yaml.UnmarshalStrict(fcontent, o.Credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed unmarshaling api server credentials configuration file '%s': %w", o.CredentialsFilePath, err)
|
return fmt.Errorf("failed unmarshaling api server credentials configuration file '%s': %w", o.CredentialsFilePath, err)
|
||||||
}
|
}
|
||||||
if o.Credentials.Login == "" || o.Credentials.Password == "" || o.Credentials.URL == "" {
|
|
||||||
log.Warningf("can't load CAPI credentials from '%s' (missing field)", o.CredentialsFilePath)
|
switch {
|
||||||
|
case o.Credentials.Login == "":
|
||||||
|
log.Warningf("can't load CAPI credentials from '%s' (missing login field)", o.CredentialsFilePath)
|
||||||
|
o.Credentials = nil
|
||||||
|
case o.Credentials.Password == "":
|
||||||
|
log.Warningf("can't load CAPI credentials from '%s' (missing password field)", o.CredentialsFilePath)
|
||||||
|
o.Credentials = nil
|
||||||
|
case o.Credentials.URL == "":
|
||||||
|
log.Warningf("can't load CAPI credentials from '%s' (missing url field)", o.CredentialsFilePath)
|
||||||
o.Credentials = nil
|
o.Credentials = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,14 +114,17 @@ func (o *OnlineApiClientCfg) Load() error {
|
||||||
|
|
||||||
func (l *LocalApiClientCfg) Load() error {
|
func (l *LocalApiClientCfg) Load() error {
|
||||||
patcher := yamlpatch.NewPatcher(l.CredentialsFilePath, ".local")
|
patcher := yamlpatch.NewPatcher(l.CredentialsFilePath, ".local")
|
||||||
|
|
||||||
fcontent, err := patcher.MergedPatchContent()
|
fcontent, err := patcher.MergedPatchContent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.UnmarshalStrict(fcontent, &l.Credentials)
|
err = yaml.UnmarshalStrict(fcontent, &l.Credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed unmarshaling api client credential configuration file '%s': %w", l.CredentialsFilePath, err)
|
return fmt.Errorf("failed unmarshaling api client credential configuration file '%s': %w", l.CredentialsFilePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Credentials == nil || l.Credentials.URL == "" {
|
if l.Credentials == nil || l.Credentials.URL == "" {
|
||||||
return fmt.Errorf("no credentials or URL found in api client configuration '%s'", l.CredentialsFilePath)
|
return fmt.Errorf("no credentials or URL found in api client configuration '%s'", l.CredentialsFilePath)
|
||||||
}
|
}
|
||||||
|
@ -137,9 +155,11 @@ func (l *LocalApiClientCfg) Load() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Error loading system CA certificates: %s", err)
|
log.Warningf("Error loading system CA certificates: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if caCertPool == nil {
|
if caCertPool == nil {
|
||||||
caCertPool = x509.NewCertPool()
|
caCertPool = x509.NewCertPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
caCertPool.AppendCertsFromPEM(caCert)
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
apiclient.CaCertPool = caCertPool
|
apiclient.CaCertPool = caCertPool
|
||||||
}
|
}
|
||||||
|
@ -160,12 +180,15 @@ func (lapiCfg *LocalApiServerCfg) GetTrustedIPs() ([]net.IPNet, error) {
|
||||||
trustedIPs := make([]net.IPNet, 0)
|
trustedIPs := make([]net.IPNet, 0)
|
||||||
for _, ip := range lapiCfg.TrustedIPs {
|
for _, ip := range lapiCfg.TrustedIPs {
|
||||||
cidr := toValidCIDR(ip)
|
cidr := toValidCIDR(ip)
|
||||||
|
|
||||||
_, ipNet, err := net.ParseCIDR(cidr)
|
_, ipNet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
trustedIPs = append(trustedIPs, *ipNet)
|
trustedIPs = append(trustedIPs, *ipNet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return trustedIPs, nil
|
return trustedIPs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +200,7 @@ func toValidCIDR(ip string) string {
|
||||||
if strings.Contains(ip, ":") {
|
if strings.Contains(ip, ":") {
|
||||||
return ip + "/128"
|
return ip + "/128"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip + "/32"
|
return ip + "/32"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,24 +351,30 @@ func parseCapiWhitelists(fd io.Reader) (*CapiWhitelist, error) {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
return nil, fmt.Errorf("empty file")
|
return nil, fmt.Errorf("empty file")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := &CapiWhitelist{
|
ret := &CapiWhitelist{
|
||||||
Ips: make([]net.IP, len(fromCfg.Ips)),
|
Ips: make([]net.IP, len(fromCfg.Ips)),
|
||||||
Cidrs: make([]*net.IPNet, len(fromCfg.Cidrs)),
|
Cidrs: make([]*net.IPNet, len(fromCfg.Cidrs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, v := range fromCfg.Ips {
|
for idx, v := range fromCfg.Ips {
|
||||||
ip := net.ParseIP(v)
|
ip := net.ParseIP(v)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, fmt.Errorf("invalid IP address: %s", v)
|
return nil, fmt.Errorf("invalid IP address: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Ips[idx] = ip
|
ret.Ips[idx] = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, v := range fromCfg.Cidrs {
|
for idx, v := range fromCfg.Cidrs {
|
||||||
_, tnet, err := net.ParseCIDR(v)
|
_, tnet, err := net.ParseCIDR(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Cidrs[idx] = tnet
|
ret.Cidrs[idx] = tnet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,10 +386,6 @@ func (s *LocalApiServerCfg) LoadCapiWhitelists() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(s.CapiWhitelistsPath); os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("capi whitelist file '%s' does not exist", s.CapiWhitelistsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := os.Open(s.CapiWhitelistsPath)
|
fd, err := os.Open(s.CapiWhitelistsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("while opening capi whitelist file: %s", err)
|
return fmt.Errorf("while opening capi whitelist file: %s", err)
|
||||||
|
|
|
@ -116,7 +116,7 @@ func TestLoadOnlineApiClientCfg(t *testing.T) {
|
||||||
CredentialsFilePath: "./testdata/nonexist_online-api-secrets.yaml",
|
CredentialsFilePath: "./testdata/nonexist_online-api-secrets.yaml",
|
||||||
},
|
},
|
||||||
expected: &ApiCredentialsCfg{},
|
expected: &ApiCredentialsCfg{},
|
||||||
expectedErr: "failed to read api server credentials",
|
expectedErr: "open ./testdata/nonexist_online-api-secrets.yaml: " + cstest.FileNotFoundMessage,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +254,7 @@ func TestLoadAPIServer(t *testing.T) {
|
||||||
func mustParseCIDRNet(t *testing.T, s string) *net.IPNet {
|
func mustParseCIDRNet(t *testing.T, s string) *net.IPNet {
|
||||||
_, ipNet, err := net.ParseCIDR(s)
|
_, ipNet, err := net.ParseCIDR(s)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return ipNet
|
return ipNet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (s *PluginSuite) TestBrokerNoThreshold() {
|
||||||
t := s.T()
|
t := s.T()
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -182,7 +182,7 @@ func (s *PluginSuite) TestBrokerNoThreshold() {
|
||||||
|
|
||||||
err = json.Unmarshal(content, &alerts)
|
err = json.Unmarshal(content, &alerts)
|
||||||
log.Printf("content-> %s", content)
|
log.Printf("content-> %s", content)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_TimeFirst() {
|
||||||
s.writeconfig(cfg)
|
s.writeconfig(cfg)
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -215,11 +215,11 @@ func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_TimeFirst() {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
// after 1 seconds, we should have data
|
// after 1 seconds, we should have data
|
||||||
content, err := os.ReadFile("./out")
|
content, err := os.ReadFile("./out")
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var alerts []models.Alert
|
var alerts []models.Alert
|
||||||
err = json.Unmarshal(content, &alerts)
|
err = json.Unmarshal(content, &alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 3)
|
assert.Len(t, alerts, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_CountFirst() {
|
||||||
s.writeconfig(cfg)
|
s.writeconfig(cfg)
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -259,7 +259,7 @@ func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_CountFirst() {
|
||||||
|
|
||||||
var alerts []models.Alert
|
var alerts []models.Alert
|
||||||
err = json.Unmarshal(content, &alerts)
|
err = json.Unmarshal(content, &alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 4)
|
assert.Len(t, alerts, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ func (s *PluginSuite) TestBrokerRunGroupThreshold() {
|
||||||
s.writeconfig(cfg)
|
s.writeconfig(cfg)
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -306,11 +306,11 @@ func (s *PluginSuite) TestBrokerRunGroupThreshold() {
|
||||||
// two notifications, one with 4 alerts, one with 2 alerts
|
// two notifications, one with 4 alerts, one with 2 alerts
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 4)
|
assert.Len(t, alerts, 4)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 2)
|
assert.Len(t, alerts, 2)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
|
@ -328,7 +328,7 @@ func (s *PluginSuite) TestBrokerRunTimeThreshold() {
|
||||||
s.writeconfig(cfg)
|
s.writeconfig(cfg)
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -348,7 +348,7 @@ func (s *PluginSuite) TestBrokerRunTimeThreshold() {
|
||||||
|
|
||||||
var alerts []models.Alert
|
var alerts []models.Alert
|
||||||
err = json.Unmarshal(content, &alerts)
|
err = json.Unmarshal(content, &alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ func (s *PluginSuite) TestBrokerRunSimple() {
|
||||||
t := s.T()
|
t := s.T()
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -382,11 +382,11 @@ func (s *PluginSuite) TestBrokerRunSimple() {
|
||||||
// two notifications, one alert each
|
// two notifications, one alert each
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (s *PluginSuite) TestBrokerRun() {
|
||||||
t := s.T()
|
t := s.T()
|
||||||
|
|
||||||
pb, err := s.InitBroker(nil)
|
pb, err := s.InitBroker(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tomb := tomb.Tomb{}
|
tomb := tomb.Tomb{}
|
||||||
go pb.Run(&tomb)
|
go pb.Run(&tomb)
|
||||||
|
@ -94,11 +94,11 @@ func (s *PluginSuite) TestBrokerRun() {
|
||||||
// two notifications, one alert each
|
// two notifications, one alert each
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, alerts, 1)
|
assert.Len(t, alerts, 1)
|
||||||
|
|
||||||
err = decoder.Decode(&alerts)
|
err = decoder.Decode(&alerts)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/antonmedv/expr"
|
"github.com/antonmedv/expr"
|
||||||
"github.com/antonmedv/expr/vm"
|
"github.com/antonmedv/expr/vm"
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
|
@ -22,19 +21,23 @@ type Runtime struct {
|
||||||
Logger *log.Entry `json:"-" yaml:"-"`
|
Logger *log.Entry `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultDuration = "4h"
|
const defaultDuration = "4h"
|
||||||
|
|
||||||
func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
profilesRuntime := make([]*Runtime, 0)
|
profilesRuntime := make([]*Runtime, 0)
|
||||||
|
|
||||||
for _, profile := range profilesCfg {
|
for _, profile := range profilesCfg {
|
||||||
var runtimeFilter, runtimeDurationExpr *vm.Program
|
var runtimeFilter, runtimeDurationExpr *vm.Program
|
||||||
|
|
||||||
runtime := &Runtime{}
|
runtime := &Runtime{}
|
||||||
|
|
||||||
xlog := log.New()
|
xlog := log.New()
|
||||||
if err := types.ConfigureLogger(xlog); err != nil {
|
if err := types.ConfigureLogger(xlog); err != nil {
|
||||||
log.Fatalf("While creating profiles-specific logger : %s", err)
|
log.Fatalf("While creating profiles-specific logger : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
xlog.SetLevel(log.InfoLevel)
|
xlog.SetLevel(log.InfoLevel)
|
||||||
runtime.Logger = xlog.WithFields(log.Fields{
|
runtime.Logger = xlog.WithFields(log.Fields{
|
||||||
"type": "profile",
|
"type": "profile",
|
||||||
|
@ -43,17 +46,20 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
||||||
|
|
||||||
runtime.RuntimeFilters = make([]*vm.Program, len(profile.Filters))
|
runtime.RuntimeFilters = make([]*vm.Program, len(profile.Filters))
|
||||||
runtime.Cfg = profile
|
runtime.Cfg = profile
|
||||||
if runtime.Cfg.OnSuccess != "" && runtime.Cfg.OnSuccess != "continue" && runtime.Cfg.OnSuccess != "break" {
|
|
||||||
return []*Runtime{}, fmt.Errorf("invalid 'on_success' for '%s': %s", profile.Name, runtime.Cfg.OnSuccess)
|
|
||||||
}
|
|
||||||
if runtime.Cfg.OnFailure != "" && runtime.Cfg.OnFailure != "continue" && runtime.Cfg.OnFailure != "break" && runtime.Cfg.OnFailure != "apply" {
|
|
||||||
return []*Runtime{}, fmt.Errorf("invalid 'on_failure' for '%s' : %s", profile.Name, runtime.Cfg.OnFailure)
|
|
||||||
}
|
|
||||||
for fIdx, filter := range profile.Filters {
|
|
||||||
|
|
||||||
|
if runtime.Cfg.OnSuccess != "" && runtime.Cfg.OnSuccess != "continue" && runtime.Cfg.OnSuccess != "break" {
|
||||||
|
return nil, fmt.Errorf("invalid 'on_success' for '%s': %s", profile.Name, runtime.Cfg.OnSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.Cfg.OnFailure != "" && runtime.Cfg.OnFailure != "continue" && runtime.Cfg.OnFailure != "break" && runtime.Cfg.OnFailure != "apply" {
|
||||||
|
return nil, fmt.Errorf("invalid 'on_failure' for '%s' : %s", profile.Name, runtime.Cfg.OnFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
for fIdx, filter := range profile.Filters {
|
||||||
if runtimeFilter, err = expr.Compile(filter, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil {
|
if runtimeFilter, err = expr.Compile(filter, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil {
|
||||||
return []*Runtime{}, errors.Wrapf(err, "error compiling filter of '%s'", profile.Name)
|
return nil, fmt.Errorf("error compiling filter of '%s': %w", profile.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.RuntimeFilters[fIdx] = runtimeFilter
|
runtime.RuntimeFilters[fIdx] = runtimeFilter
|
||||||
if profile.Debug != nil && *profile.Debug {
|
if profile.Debug != nil && *profile.Debug {
|
||||||
runtime.Logger.Logger.SetLevel(log.DebugLevel)
|
runtime.Logger.Logger.SetLevel(log.DebugLevel)
|
||||||
|
@ -62,8 +68,9 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
||||||
|
|
||||||
if profile.DurationExpr != "" {
|
if profile.DurationExpr != "" {
|
||||||
if runtimeDurationExpr, err = expr.Compile(profile.DurationExpr, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil {
|
if runtimeDurationExpr, err = expr.Compile(profile.DurationExpr, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil {
|
||||||
return []*Runtime{}, errors.Wrapf(err, "error compiling duration_expr of %s", profile.Name)
|
return nil, fmt.Errorf("error compiling duration_expr of %s: %w", profile.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.RuntimeDurationExpr = runtimeDurationExpr
|
runtime.RuntimeDurationExpr = runtimeDurationExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,14 +83,16 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
||||||
runtime.Logger.Warningf("No duration specified for %s, using default duration %s", profile.Name, defaultDuration)
|
runtime.Logger.Warningf("No duration specified for %s, using default duration %s", profile.Name, defaultDuration)
|
||||||
duration = defaultDuration
|
duration = defaultDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := time.ParseDuration(duration); err != nil {
|
if _, err := time.ParseDuration(duration); err != nil {
|
||||||
return []*Runtime{}, errors.Wrapf(err, "error parsing duration '%s' of %s", duration, profile.Name)
|
return nil, fmt.Errorf("error parsing duration '%s' of %s: %w", duration, profile.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profilesRuntime = append(profilesRuntime, runtime)
|
profilesRuntime = append(profilesRuntime, runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return profilesRuntime, nil
|
return profilesRuntime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,30 +119,29 @@ func (Profile *Runtime) GenerateDecisionFromProfile(Alert *models.Alert) ([]*mod
|
||||||
*decision.Scope = *Alert.Source.Scope
|
*decision.Scope = *Alert.Source.Scope
|
||||||
}
|
}
|
||||||
/*some fields are populated from the reference object : duration, scope, type*/
|
/*some fields are populated from the reference object : duration, scope, type*/
|
||||||
|
|
||||||
decision.Duration = new(string)
|
decision.Duration = new(string)
|
||||||
|
if refDecision.Duration != nil {
|
||||||
|
*decision.Duration = *refDecision.Duration
|
||||||
|
}
|
||||||
|
|
||||||
if Profile.Cfg.DurationExpr != "" && Profile.RuntimeDurationExpr != nil {
|
if Profile.Cfg.DurationExpr != "" && Profile.RuntimeDurationExpr != nil {
|
||||||
profileDebug := false
|
profileDebug := false
|
||||||
if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug {
|
if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug {
|
||||||
profileDebug = true
|
profileDebug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
duration, err := exprhelpers.Run(Profile.RuntimeDurationExpr, map[string]interface{}{"Alert": Alert}, Profile.Logger, profileDebug)
|
duration, err := exprhelpers.Run(Profile.RuntimeDurationExpr, map[string]interface{}{"Alert": Alert}, Profile.Logger, profileDebug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Profile.Logger.Warningf("Failed to run duration_expr : %v", err)
|
Profile.Logger.Warningf("Failed to run duration_expr : %v", err)
|
||||||
*decision.Duration = *refDecision.Duration
|
|
||||||
} else {
|
} else {
|
||||||
durationStr := fmt.Sprint(duration)
|
durationStr := fmt.Sprint(duration)
|
||||||
if _, err := time.ParseDuration(durationStr); err != nil {
|
if _, err := time.ParseDuration(durationStr); err != nil {
|
||||||
Profile.Logger.Warningf("Failed to parse expr duration result '%s'", duration)
|
Profile.Logger.Warningf("Failed to parse expr duration result '%s'", duration)
|
||||||
*decision.Duration = *refDecision.Duration
|
|
||||||
} else {
|
} else {
|
||||||
*decision.Duration = durationStr
|
*decision.Duration = durationStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if refDecision.Duration == nil {
|
|
||||||
*decision.Duration = defaultDuration
|
|
||||||
}
|
|
||||||
*decision.Duration = *refDecision.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decision.Type = new(string)
|
decision.Type = new(string)
|
||||||
|
@ -144,13 +152,16 @@ func (Profile *Runtime) GenerateDecisionFromProfile(Alert *models.Alert) ([]*mod
|
||||||
*decision.Value = *Alert.Source.Value
|
*decision.Value = *Alert.Source.Value
|
||||||
decision.Origin = new(string)
|
decision.Origin = new(string)
|
||||||
*decision.Origin = types.CrowdSecOrigin
|
*decision.Origin = types.CrowdSecOrigin
|
||||||
|
|
||||||
if refDecision.Origin != nil {
|
if refDecision.Origin != nil {
|
||||||
*decision.Origin = fmt.Sprintf("%s/%s", *decision.Origin, *refDecision.Origin)
|
*decision.Origin = fmt.Sprintf("%s/%s", *decision.Origin, *refDecision.Origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
decision.Scenario = new(string)
|
decision.Scenario = new(string)
|
||||||
*decision.Scenario = *Alert.Scenario
|
*decision.Scenario = *Alert.Scenario
|
||||||
decisions = append(decisions, &decision)
|
decisions = append(decisions, &decision)
|
||||||
}
|
}
|
||||||
|
|
||||||
return decisions, nil
|
return decisions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,16 +170,19 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision
|
||||||
var decisions []*models.Decision
|
var decisions []*models.Decision
|
||||||
|
|
||||||
matched := false
|
matched := false
|
||||||
|
|
||||||
for eIdx, expression := range Profile.RuntimeFilters {
|
for eIdx, expression := range Profile.RuntimeFilters {
|
||||||
debugProfile := false
|
debugProfile := false
|
||||||
if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug {
|
if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug {
|
||||||
debugProfile = true
|
debugProfile = true
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := exprhelpers.Run(expression, map[string]interface{}{"Alert": Alert}, Profile.Logger, debugProfile)
|
output, err := exprhelpers.Run(expression, map[string]interface{}{"Alert": Alert}, Profile.Logger, debugProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Profile.Logger.Warningf("failed to run profile expr for %s : %v", Profile.Cfg.Name, err)
|
Profile.Logger.Warningf("failed to run profile expr for %s: %v", Profile.Cfg.Name, err)
|
||||||
return nil, matched, errors.Wrapf(err, "while running expression %s", Profile.Cfg.Filters[eIdx])
|
return nil, matched, fmt.Errorf("while running expression %s: %w", Profile.Cfg.Filters[eIdx], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch out := output.(type) {
|
switch out := output.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
if out {
|
if out {
|
||||||
|
@ -176,7 +190,7 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision
|
||||||
/*the expression matched, create the associated decision*/
|
/*the expression matched, create the associated decision*/
|
||||||
subdecisions, err := Profile.GenerateDecisionFromProfile(Alert)
|
subdecisions, err := Profile.GenerateDecisionFromProfile(Alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, matched, errors.Wrapf(err, "while generating decision from profile %s", Profile.Cfg.Name)
|
return nil, matched, fmt.Errorf("while generating decision from profile %s: %w", Profile.Cfg.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
decisions = append(decisions, subdecisions...)
|
decisions = append(decisions, subdecisions...)
|
||||||
|
@ -189,9 +203,7 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, matched, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, Profile.Cfg.Filters[eIdx])
|
return nil, matched, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, Profile.Cfg.Filters[eIdx])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return decisions, matched, nil
|
return decisions, matched, nil
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
)
|
)
|
||||||
|
@ -36,25 +37,30 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
// wip
|
// wip
|
||||||
func fireHandler(req *http.Request) *http.Response {
|
func fireHandler(req *http.Request) *http.Response {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
apiKey := req.Header.Get("x-api-key")
|
apiKey := req.Header.Get("x-api-key")
|
||||||
if apiKey != validApiKey {
|
if apiKey != validApiKey {
|
||||||
log.Warningf("invalid api key: %s", apiKey)
|
log.Warningf("invalid api key: %s", apiKey)
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusForbidden,
|
StatusCode: http.StatusForbidden,
|
||||||
Body: nil,
|
Body: nil,
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//unmarshal data
|
//unmarshal data
|
||||||
if fireResponses == nil {
|
if fireResponses == nil {
|
||||||
page1, err := os.ReadFile("tests/fire-page1.json")
|
page1, err := os.ReadFile("tests/fire-page1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("can't read file")
|
panic("can't read file")
|
||||||
}
|
}
|
||||||
|
|
||||||
page2, err := os.ReadFile("tests/fire-page2.json")
|
page2, err := os.ReadFile("tests/fire-page2.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("can't read file")
|
panic("can't read file")
|
||||||
}
|
}
|
||||||
|
|
||||||
fireResponses = []string{string(page1), string(page2)}
|
fireResponses = []string{string(page1), string(page2)}
|
||||||
}
|
}
|
||||||
//let's assume we have two valid pages.
|
//let's assume we have two valid pages.
|
||||||
|
@ -70,6 +76,7 @@ func fireHandler(req *http.Request) *http.Response {
|
||||||
//how to react if you give a page number that is too big ?
|
//how to react if you give a page number that is too big ?
|
||||||
if page > len(fireResponses) {
|
if page > len(fireResponses) {
|
||||||
log.Warningf(" page too big %d vs %d", page, len(fireResponses))
|
log.Warningf(" page too big %d vs %d", page, len(fireResponses))
|
||||||
|
|
||||||
emptyResponse := `{
|
emptyResponse := `{
|
||||||
"_links": {
|
"_links": {
|
||||||
"first": {
|
"first": {
|
||||||
|
@ -82,8 +89,10 @@ func fireHandler(req *http.Request) *http.Response {
|
||||||
"items": []
|
"items": []
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(emptyResponse))}
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(emptyResponse))}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := io.NopCloser(strings.NewReader(fireResponses[page-1]))
|
reader := io.NopCloser(strings.NewReader(fireResponses[page-1]))
|
||||||
//we should care about limit too
|
//we should care about limit too
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
|
@ -106,6 +115,7 @@ func smokeHandler(req *http.Request) *http.Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedIP := strings.Split(req.URL.Path, "/")[3]
|
requestedIP := strings.Split(req.URL.Path, "/")[3]
|
||||||
|
|
||||||
response, ok := smokeResponses[requestedIP]
|
response, ok := smokeResponses[requestedIP]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
|
@ -135,6 +145,7 @@ func rateLimitedHandler(req *http.Request) *http.Response {
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusTooManyRequests,
|
StatusCode: http.StatusTooManyRequests,
|
||||||
Body: nil,
|
Body: nil,
|
||||||
|
@ -151,7 +162,9 @@ func searchHandler(req *http.Request) *http.Response {
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url, _ := url.Parse(req.URL.String())
|
url, _ := url.Parse(req.URL.String())
|
||||||
|
|
||||||
ipsParam := url.Query().Get("ips")
|
ipsParam := url.Query().Get("ips")
|
||||||
if ipsParam == "" {
|
if ipsParam == "" {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
|
@ -163,6 +176,7 @@ func searchHandler(req *http.Request) *http.Response {
|
||||||
|
|
||||||
totalIps := 0
|
totalIps := 0
|
||||||
notFound := 0
|
notFound := 0
|
||||||
|
|
||||||
ips := strings.Split(ipsParam, ",")
|
ips := strings.Split(ipsParam, ",")
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
_, ok := smokeResponses[ip]
|
_, ok := smokeResponses[ip]
|
||||||
|
@ -172,12 +186,15 @@ func searchHandler(req *http.Request) *http.Response {
|
||||||
notFound++
|
notFound++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response := fmt.Sprintf(`{"total": %d, "not_found": %d, "items": [`, totalIps, notFound)
|
response := fmt.Sprintf(`{"total": %d, "not_found": %d, "items": [`, totalIps, notFound)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
response += smokeResponses[ip]
|
response += smokeResponses[ip]
|
||||||
}
|
}
|
||||||
|
|
||||||
response += "]}"
|
response += "]}"
|
||||||
reader := io.NopCloser(strings.NewReader(response))
|
reader := io.NopCloser(strings.NewReader(response))
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: reader,
|
Body: reader,
|
||||||
|
@ -190,7 +207,7 @@ func TestBadFireAuth(t *testing.T) {
|
||||||
Transport: RoundTripFunc(fireHandler),
|
Transport: RoundTripFunc(fireHandler),
|
||||||
}))
|
}))
|
||||||
_, err := ctiClient.Fire(FireParams{})
|
_, err := ctiClient.Fire(FireParams{})
|
||||||
assert.EqualError(t, err, ErrUnauthorized.Error())
|
require.EqualError(t, err, ErrUnauthorized.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireOk(t *testing.T) {
|
func TestFireOk(t *testing.T) {
|
||||||
|
@ -198,19 +215,19 @@ func TestFireOk(t *testing.T) {
|
||||||
Transport: RoundTripFunc(fireHandler),
|
Transport: RoundTripFunc(fireHandler),
|
||||||
}))
|
}))
|
||||||
data, err := cticlient.Fire(FireParams{})
|
data, err := cticlient.Fire(FireParams{})
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(data.Items), 3)
|
assert.Len(t, data.Items, 3)
|
||||||
assert.Equal(t, data.Items[0].Ip, "1.2.3.4")
|
assert.Equal(t, "1.2.3.4", data.Items[0].Ip)
|
||||||
//page 1 is the default
|
//page 1 is the default
|
||||||
data, err = cticlient.Fire(FireParams{Page: ptr.Of(1)})
|
data, err = cticlient.Fire(FireParams{Page: ptr.Of(1)})
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(data.Items), 3)
|
assert.Len(t, data.Items, 3)
|
||||||
assert.Equal(t, data.Items[0].Ip, "1.2.3.4")
|
assert.Equal(t, "1.2.3.4", data.Items[0].Ip)
|
||||||
//page 2
|
//page 2
|
||||||
data, err = cticlient.Fire(FireParams{Page: ptr.Of(2)})
|
data, err = cticlient.Fire(FireParams{Page: ptr.Of(2)})
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(data.Items), 3)
|
assert.Len(t, data.Items, 3)
|
||||||
assert.Equal(t, data.Items[0].Ip, "4.2.3.4")
|
assert.Equal(t, "4.2.3.4", data.Items[0].Ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirePaginator(t *testing.T) {
|
func TestFirePaginator(t *testing.T) {
|
||||||
|
@ -219,17 +236,16 @@ func TestFirePaginator(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
paginator := NewFirePaginator(cticlient, FireParams{})
|
paginator := NewFirePaginator(cticlient, FireParams{})
|
||||||
items, err := paginator.Next()
|
items, err := paginator.Next()
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(items), 3)
|
assert.Len(t, items, 3)
|
||||||
assert.Equal(t, items[0].Ip, "1.2.3.4")
|
assert.Equal(t, "1.2.3.4", items[0].Ip)
|
||||||
items, err = paginator.Next()
|
items, err = paginator.Next()
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(items), 3)
|
assert.Len(t, items, 3)
|
||||||
assert.Equal(t, items[0].Ip, "4.2.3.4")
|
assert.Equal(t, "4.2.3.4", items[0].Ip)
|
||||||
items, err = paginator.Next()
|
items, err = paginator.Next()
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(items), 0)
|
assert.Empty(t, items)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBadSmokeAuth(t *testing.T) {
|
func TestBadSmokeAuth(t *testing.T) {
|
||||||
|
@ -237,13 +253,14 @@ func TestBadSmokeAuth(t *testing.T) {
|
||||||
Transport: RoundTripFunc(smokeHandler),
|
Transport: RoundTripFunc(smokeHandler),
|
||||||
}))
|
}))
|
||||||
_, err := ctiClient.GetIPInfo("1.1.1.1")
|
_, err := ctiClient.GetIPInfo("1.1.1.1")
|
||||||
assert.EqualError(t, err, ErrUnauthorized.Error())
|
require.EqualError(t, err, ErrUnauthorized.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSmokeInfoValidIP(t *testing.T) {
|
func TestSmokeInfoValidIP(t *testing.T) {
|
||||||
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
||||||
Transport: RoundTripFunc(smokeHandler),
|
Transport: RoundTripFunc(smokeHandler),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := ctiClient.GetIPInfo("1.1.1.1")
|
resp, err := ctiClient.GetIPInfo("1.1.1.1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get ip info: %s", err)
|
t.Fatalf("failed to get ip info: %s", err)
|
||||||
|
@ -257,6 +274,7 @@ func TestSmokeUnknownIP(t *testing.T) {
|
||||||
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
||||||
Transport: RoundTripFunc(smokeHandler),
|
Transport: RoundTripFunc(smokeHandler),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := ctiClient.GetIPInfo("42.42.42.42")
|
resp, err := ctiClient.GetIPInfo("42.42.42.42")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get ip info: %s", err)
|
t.Fatalf("failed to get ip info: %s", err)
|
||||||
|
@ -270,20 +288,22 @@ func TestRateLimit(t *testing.T) {
|
||||||
Transport: RoundTripFunc(rateLimitedHandler),
|
Transport: RoundTripFunc(rateLimitedHandler),
|
||||||
}))
|
}))
|
||||||
_, err := ctiClient.GetIPInfo("1.1.1.1")
|
_, err := ctiClient.GetIPInfo("1.1.1.1")
|
||||||
assert.EqualError(t, err, ErrLimit.Error())
|
require.EqualError(t, err, ErrLimit.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchIPs(t *testing.T) {
|
func TestSearchIPs(t *testing.T) {
|
||||||
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
ctiClient := NewCrowdsecCTIClient(WithAPIKey(validApiKey), WithHTTPClient(&http.Client{
|
||||||
Transport: RoundTripFunc(searchHandler),
|
Transport: RoundTripFunc(searchHandler),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := ctiClient.SearchIPs([]string{"1.1.1.1", "42.42.42.42"})
|
resp, err := ctiClient.SearchIPs([]string{"1.1.1.1", "42.42.42.42"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to search ips: %s", err)
|
t.Fatalf("failed to search ips: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, 1, resp.Total)
|
assert.Equal(t, 1, resp.Total)
|
||||||
assert.Equal(t, 1, resp.NotFound)
|
assert.Equal(t, 1, resp.NotFound)
|
||||||
assert.Equal(t, 1, len(resp.Items))
|
assert.Len(t, resp.Items, 1)
|
||||||
assert.Equal(t, "1.1.1.1", resp.Items[0].Ip)
|
assert.Equal(t, "1.1.1.1", resp.Items[0].Ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,27 +88,28 @@ func getSampleSmokeItem() SmokeItem {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return emptyItem
|
return emptyItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasicSmokeItem(t *testing.T) {
|
func TestBasicSmokeItem(t *testing.T) {
|
||||||
item := getSampleSmokeItem()
|
item := getSampleSmokeItem()
|
||||||
assert.Equal(t, item.GetAttackDetails(), []string{"ssh:bruteforce"})
|
assert.Equal(t, []string{"ssh:bruteforce"}, item.GetAttackDetails())
|
||||||
assert.Equal(t, item.GetBehaviors(), []string{"ssh:bruteforce"})
|
assert.Equal(t, []string{"ssh:bruteforce"}, item.GetBehaviors())
|
||||||
assert.Equal(t, item.GetMaliciousnessScore(), float32(0.1))
|
assert.InDelta(t, 0.1, item.GetMaliciousnessScore(), 0.000001)
|
||||||
assert.Equal(t, item.IsPartOfCommunityBlocklist(), false)
|
assert.False(t, item.IsPartOfCommunityBlocklist())
|
||||||
assert.Equal(t, item.GetBackgroundNoiseScore(), int(3))
|
assert.Equal(t, 3, item.GetBackgroundNoiseScore())
|
||||||
assert.Equal(t, item.GetFalsePositives(), []string{})
|
assert.Equal(t, []string{}, item.GetFalsePositives())
|
||||||
assert.Equal(t, item.IsFalsePositive(), false)
|
assert.False(t, item.IsFalsePositive())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptySmokeItem(t *testing.T) {
|
func TestEmptySmokeItem(t *testing.T) {
|
||||||
item := SmokeItem{}
|
item := SmokeItem{}
|
||||||
assert.Equal(t, item.GetAttackDetails(), []string{})
|
assert.Equal(t, []string{}, item.GetAttackDetails())
|
||||||
assert.Equal(t, item.GetBehaviors(), []string{})
|
assert.Equal(t, []string{}, item.GetBehaviors())
|
||||||
assert.Equal(t, item.GetMaliciousnessScore(), float32(0.0))
|
assert.InDelta(t, 0.0, item.GetMaliciousnessScore(), 0)
|
||||||
assert.Equal(t, item.IsPartOfCommunityBlocklist(), false)
|
assert.False(t, item.IsPartOfCommunityBlocklist())
|
||||||
assert.Equal(t, item.GetBackgroundNoiseScore(), int(0))
|
assert.Equal(t, 0, item.GetBackgroundNoiseScore())
|
||||||
assert.Equal(t, item.GetFalsePositives(), []string{})
|
assert.Equal(t, []string{}, item.GetFalsePositives())
|
||||||
assert.Equal(t, item.IsFalsePositive(), false)
|
assert.False(t, item.IsFalsePositive())
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ func downloadFile(url string, destPath string) error {
|
||||||
// if the remote has no modification date, but local file has been modified > a week ago, update.
|
// if the remote has no modification date, but local file has been modified > a week ago, update.
|
||||||
func needsUpdate(destPath string, url string, logger *logrus.Logger) bool {
|
func needsUpdate(destPath string, url string, logger *logrus.Logger) bool {
|
||||||
fileInfo, err := os.Stat(destPath)
|
fileInfo, err := os.Stat(destPath)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
return true
|
return true
|
||||||
|
@ -89,6 +90,7 @@ func needsUpdate(destPath string, url string, logger *logrus.Logger) bool {
|
||||||
if localIsOld {
|
if localIsOld {
|
||||||
logger.Infof("no last modified date for %s, but local file is older than %s", url, shelfLife)
|
logger.Infof("no last modified date for %s, but local file is older than %s", url, shelfLife)
|
||||||
}
|
}
|
||||||
|
|
||||||
return localIsOld
|
return localIsOld
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +131,7 @@ func downloadDataSet(dataFolder string, force bool, reader io.Reader, logger *lo
|
||||||
|
|
||||||
if force || needsUpdate(destPath, dataS.SourceURL, logger) {
|
if force || needsUpdate(destPath, dataS.SourceURL, logger) {
|
||||||
logger.Debugf("downloading %s in %s", dataS.SourceURL, destPath)
|
logger.Debugf("downloading %s in %s", dataS.SourceURL, destPath)
|
||||||
|
|
||||||
if err := downloadFile(dataS.SourceURL, destPath); err != nil {
|
if err := downloadFile(dataS.SourceURL, destPath); err != nil {
|
||||||
return fmt.Errorf("while getting data: %w", err)
|
return fmt.Errorf("while getting data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
)
|
)
|
||||||
|
@ -97,6 +97,10 @@ func (h *Hub) parseIndex() error {
|
||||||
item.FileName = path.Base(item.RemotePath)
|
item.FileName = path.Base(item.RemotePath)
|
||||||
|
|
||||||
item.logMissingSubItems()
|
item.logMissingSubItems()
|
||||||
|
|
||||||
|
if item.latestHash() == "" {
|
||||||
|
h.logger.Errorf("invalid hub item %s: latest version missing from index", item.FQName())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
func TestInitHubUpdate(t *testing.T) {
|
func TestInitHubUpdate(t *testing.T) {
|
||||||
hub := envSetup(t)
|
hub := envSetup(t)
|
||||||
|
|
||||||
remote := &RemoteHubCfg{
|
remote := &RemoteHubCfg{
|
||||||
URLTemplate: mockURLTemplate,
|
URLTemplate: mockURLTemplate,
|
||||||
Branch: "master",
|
Branch: "master",
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/enescakir/emoji"
|
"github.com/enescakir/emoji"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -440,3 +440,15 @@ func (i *Item) addTaint(sub *Item) {
|
||||||
ancestor.addTaint(sub)
|
ancestor.addTaint(sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// latestHash() returns the hash of the latest version of the item.
|
||||||
|
// if it's missing, the index file has been manually modified or got corrupted.
|
||||||
|
func (i *Item) latestHash() string {
|
||||||
|
for k, v := range i.Versions {
|
||||||
|
if k == i.Version {
|
||||||
|
return v.Digest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (i *Item) Install(force bool, downloadOnly bool) error {
|
||||||
|
|
||||||
filePath, err := i.downloadLatest(force, true)
|
filePath, err := i.downloadLatest(force, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("while downloading %s: %w", i.Name, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if downloadOnly {
|
if downloadOnly {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cwhub
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -82,14 +83,14 @@ func (i *Item) downloadLatest(overwrite bool, updateOnly bool) (string, error) {
|
||||||
i.hub.logger.Tracef("collection, recurse")
|
i.hub.logger.Tracef("collection, recurse")
|
||||||
|
|
||||||
if _, err := sub.downloadLatest(overwrite, updateOnly); err != nil {
|
if _, err := sub.downloadLatest(overwrite, updateOnly); err != nil {
|
||||||
return "", fmt.Errorf("while downloading %s: %w", sub.Name, err)
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloaded := sub.State.Downloaded
|
downloaded := sub.State.Downloaded
|
||||||
|
|
||||||
if _, err := sub.download(overwrite); err != nil {
|
if _, err := sub.download(overwrite); err != nil {
|
||||||
return "", fmt.Errorf("while downloading %s: %w", sub.Name, err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to enable an item when it has been added to a collection since latest release of the collection.
|
// We need to enable an item when it has been added to a collection since latest release of the collection.
|
||||||
|
@ -108,7 +109,7 @@ func (i *Item) downloadLatest(overwrite bool, updateOnly bool) (string, error) {
|
||||||
|
|
||||||
ret, err := i.download(overwrite)
|
ret, err := i.download(overwrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to download item: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
@ -116,6 +117,10 @@ func (i *Item) downloadLatest(overwrite bool, updateOnly bool) (string, error) {
|
||||||
|
|
||||||
// FetchLatest downloads the latest item from the hub, verifies the hash and returns the content and the used url.
|
// FetchLatest downloads the latest item from the hub, verifies the hash and returns the content and the used url.
|
||||||
func (i *Item) FetchLatest() ([]byte, string, error) {
|
func (i *Item) FetchLatest() ([]byte, string, error) {
|
||||||
|
if i.latestHash() == "" {
|
||||||
|
return nil, "", errors.New("latest hash missing from index")
|
||||||
|
}
|
||||||
|
|
||||||
url, err := i.hub.remote.urlTo(i.RemotePath)
|
url, err := i.hub.remote.urlTo(i.RemotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to build request: %w", err)
|
return nil, "", fmt.Errorf("failed to build request: %w", err)
|
||||||
|
@ -146,7 +151,7 @@ func (i *Item) FetchLatest() ([]byte, string, error) {
|
||||||
i.hub.logger.Errorf("Downloaded version doesn't match index, please 'hub update'")
|
i.hub.logger.Errorf("Downloaded version doesn't match index, please 'hub update'")
|
||||||
i.hub.logger.Debugf("got %s, expected %s", meow, i.Versions[i.Version].Digest)
|
i.hub.logger.Debugf("got %s, expected %s", meow, i.Versions[i.Version].Digest)
|
||||||
|
|
||||||
return nil, "", fmt.Errorf("invalid download hash for %s", i.Name)
|
return nil, "", fmt.Errorf("invalid download hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
return body, url, nil
|
return body, url, nil
|
||||||
|
@ -180,7 +185,12 @@ func (i *Item) download(overwrite bool) (string, error) {
|
||||||
|
|
||||||
body, url, err := i.FetchLatest()
|
body, url, err := i.FetchLatest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("while downloading %s: %w", url, err)
|
what := i.Name
|
||||||
|
if url != "" {
|
||||||
|
what += " from " + url
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("while downloading %s: %w", what, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// all good, install
|
// all good, install
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
// We expect the new scenario to be installed.
|
// We expect the new scenario to be installed.
|
||||||
func TestUpgradeItemNewScenarioInCollection(t *testing.T) {
|
func TestUpgradeItemNewScenarioInCollection(t *testing.T) {
|
||||||
hub := envSetup(t)
|
hub := envSetup(t)
|
||||||
|
|
||||||
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
||||||
|
|
||||||
// fresh install of collection
|
// fresh install of collection
|
||||||
|
@ -65,7 +64,6 @@ func TestUpgradeItemNewScenarioInCollection(t *testing.T) {
|
||||||
// Upgrade should install should not enable/download the disabled scenario.
|
// Upgrade should install should not enable/download the disabled scenario.
|
||||||
func TestUpgradeItemInDisabledScenarioShouldNotBeInstalled(t *testing.T) {
|
func TestUpgradeItemInDisabledScenarioShouldNotBeInstalled(t *testing.T) {
|
||||||
hub := envSetup(t)
|
hub := envSetup(t)
|
||||||
|
|
||||||
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
||||||
|
|
||||||
// fresh install of collection
|
// fresh install of collection
|
||||||
|
@ -127,7 +125,6 @@ func getHubOrFail(t *testing.T, local *csconfig.LocalHubCfg, remote *RemoteHubCf
|
||||||
// Upgrade should install and enable the newly added scenario.
|
// Upgrade should install and enable the newly added scenario.
|
||||||
func TestUpgradeItemNewScenarioIsInstalledWhenReferencedScenarioIsDisabled(t *testing.T) {
|
func TestUpgradeItemNewScenarioIsInstalledWhenReferencedScenarioIsDisabled(t *testing.T) {
|
||||||
hub := envSetup(t)
|
hub := envSetup(t)
|
||||||
|
|
||||||
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
item := hub.GetItem(COLLECTIONS, "crowdsecurity/test_collection")
|
||||||
|
|
||||||
// fresh install of collection
|
// fresh install of collection
|
||||||
|
|
|
@ -7,13 +7,13 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isYAMLFileName(path string) bool {
|
func isYAMLFileName(path string) bool {
|
||||||
|
|
32
pkg/dumps/bucket_dump.go
Normal file
32
pkg/dumps/bucket_dump.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package dumps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketPourInfo map[string][]types.Event
|
||||||
|
|
||||||
|
func LoadBucketPourDump(filepath string) (*BucketPourInfo, error) {
|
||||||
|
dumpData, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dumpData.Close()
|
||||||
|
|
||||||
|
results, err := io.ReadAll(dumpData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucketDump BucketPourInfo
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(results, &bucketDump); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bucketDump, nil
|
||||||
|
}
|
319
pkg/dumps/parser_dump.go
Normal file
319
pkg/dumps/parser_dump.go
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
package dumps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/maptools"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
diff "github.com/r3labs/diff/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParserResult struct {
|
||||||
|
Idx int
|
||||||
|
Evt types.Event
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParserResults map[string]map[string][]ParserResult
|
||||||
|
|
||||||
|
type DumpOpts struct {
|
||||||
|
Details bool
|
||||||
|
SkipOk bool
|
||||||
|
ShowNotOkParsers bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadParserDump(filepath string) (*ParserResults, error) {
|
||||||
|
dumpData, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dumpData.Close()
|
||||||
|
|
||||||
|
results, err := io.ReadAll(dumpData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pdump := ParserResults{}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(results, &pdump); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we know that some variables should always be set,
|
||||||
|
let's check if they're present in last parser output of last stage */
|
||||||
|
|
||||||
|
stages := maptools.SortedKeys(pdump)
|
||||||
|
|
||||||
|
var lastStage string
|
||||||
|
|
||||||
|
//Loop over stages to find last successful one with at least one parser
|
||||||
|
for i := len(stages) - 2; i >= 0; i-- {
|
||||||
|
if len(pdump[stages[i]]) != 0 {
|
||||||
|
lastStage = stages[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsers := make([]string, 0, len(pdump[lastStage]))
|
||||||
|
|
||||||
|
for k := range pdump[lastStage] {
|
||||||
|
parsers = append(parsers, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(parsers)
|
||||||
|
|
||||||
|
if len(parsers) == 0 {
|
||||||
|
return nil, fmt.Errorf("no parser found. Please install the appropriate parser and retry")
|
||||||
|
}
|
||||||
|
|
||||||
|
lastParser := parsers[len(parsers)-1]
|
||||||
|
|
||||||
|
for idx, result := range pdump[lastStage][lastParser] {
|
||||||
|
if result.Evt.StrTime == "" {
|
||||||
|
log.Warningf("Line %d/%d is missing evt.StrTime. It is most likely a mistake as it will prevent your logs to be processed in time-machine/forensic mode.", idx, len(pdump[lastStage][lastParser]))
|
||||||
|
} else {
|
||||||
|
log.Debugf("Line %d/%d has evt.StrTime set to '%s'", idx, len(pdump[lastStage][lastParser]), result.Evt.StrTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pdump, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DumpTree(parserResults ParserResults, bucketPour BucketPourInfo, opts DumpOpts) {
|
||||||
|
//note : we can use line -> time as the unique identifier (of acquisition)
|
||||||
|
state := make(map[time.Time]map[string]map[string]ParserResult)
|
||||||
|
assoc := make(map[time.Time]string, 0)
|
||||||
|
parser_order := make(map[string][]string)
|
||||||
|
|
||||||
|
for stage, parsers := range parserResults {
|
||||||
|
//let's process parsers in the order according to idx
|
||||||
|
parser_order[stage] = make([]string, len(parsers))
|
||||||
|
for pname, parser := range parsers {
|
||||||
|
if len(parser) > 0 {
|
||||||
|
parser_order[stage][parser[0].Idx-1] = pname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, parser := range parser_order[stage] {
|
||||||
|
results := parsers[parser]
|
||||||
|
for _, parserRes := range results {
|
||||||
|
evt := parserRes.Evt
|
||||||
|
if _, ok := state[evt.Line.Time]; !ok {
|
||||||
|
state[evt.Line.Time] = make(map[string]map[string]ParserResult)
|
||||||
|
assoc[evt.Line.Time] = evt.Line.Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state[evt.Line.Time][stage]; !ok {
|
||||||
|
state[evt.Line.Time][stage] = make(map[string]ParserResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
state[evt.Line.Time][stage][parser] = ParserResult{Evt: evt, Success: parserRes.Success}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for bname, evtlist := range bucketPour {
|
||||||
|
for _, evt := range evtlist {
|
||||||
|
if evt.Line.Raw == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//it might be bucket overflow being reprocessed, skip this
|
||||||
|
if _, ok := state[evt.Line.Time]; !ok {
|
||||||
|
state[evt.Line.Time] = make(map[string]map[string]ParserResult)
|
||||||
|
assoc[evt.Line.Time] = evt.Line.Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
||||||
|
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
||||||
|
if _, ok := state[evt.Line.Time]["buckets"]; !ok {
|
||||||
|
state[evt.Line.Time]["buckets"] = make(map[string]ParserResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
state[evt.Line.Time]["buckets"][bname] = ParserResult{Success: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yellow := color.New(color.FgYellow).SprintFunc()
|
||||||
|
red := color.New(color.FgRed).SprintFunc()
|
||||||
|
green := color.New(color.FgGreen).SprintFunc()
|
||||||
|
whitelistReason := ""
|
||||||
|
//get each line
|
||||||
|
for tstamp, rawstr := range assoc {
|
||||||
|
if opts.SkipOk {
|
||||||
|
if _, ok := state[tstamp]["buckets"]["OK"]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("line: %s\n", rawstr)
|
||||||
|
|
||||||
|
skeys := make([]string, 0, len(state[tstamp]))
|
||||||
|
|
||||||
|
for k := range state[tstamp] {
|
||||||
|
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
||||||
|
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
||||||
|
if k == "buckets" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
skeys = append(skeys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(skeys)
|
||||||
|
|
||||||
|
// iterate stage
|
||||||
|
var prevItem types.Event
|
||||||
|
|
||||||
|
for _, stage := range skeys {
|
||||||
|
parsers := state[tstamp][stage]
|
||||||
|
|
||||||
|
sep := "├"
|
||||||
|
presep := "|"
|
||||||
|
|
||||||
|
fmt.Printf("\t%s %s\n", sep, stage)
|
||||||
|
|
||||||
|
for idx, parser := range parser_order[stage] {
|
||||||
|
res := parsers[parser].Success
|
||||||
|
sep := "├"
|
||||||
|
|
||||||
|
if idx == len(parser_order[stage])-1 {
|
||||||
|
sep = "└"
|
||||||
|
}
|
||||||
|
|
||||||
|
created := 0
|
||||||
|
updated := 0
|
||||||
|
deleted := 0
|
||||||
|
whitelisted := false
|
||||||
|
changeStr := ""
|
||||||
|
detailsDisplay := ""
|
||||||
|
|
||||||
|
if res {
|
||||||
|
changelog, _ := diff.Diff(prevItem, parsers[parser].Evt)
|
||||||
|
for _, change := range changelog {
|
||||||
|
switch change.Type {
|
||||||
|
case "create":
|
||||||
|
created++
|
||||||
|
|
||||||
|
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), green(change.To))
|
||||||
|
case "update":
|
||||||
|
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s -> %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), change.From, yellow(change.To))
|
||||||
|
|
||||||
|
if change.Path[0] == "Whitelisted" && change.To == true {
|
||||||
|
whitelisted = true
|
||||||
|
|
||||||
|
if whitelistReason == "" {
|
||||||
|
whitelistReason = parsers[parser].Evt.WhitelistReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updated++
|
||||||
|
case "delete":
|
||||||
|
deleted++
|
||||||
|
|
||||||
|
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s\n", presep, sep, change.Type, red(strings.Join(change.Path, ".")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevItem = parsers[parser].Evt
|
||||||
|
}
|
||||||
|
|
||||||
|
if created > 0 {
|
||||||
|
changeStr += green(fmt.Sprintf("+%d", created))
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated > 0 {
|
||||||
|
if len(changeStr) > 0 {
|
||||||
|
changeStr += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStr += yellow(fmt.Sprintf("~%d", updated))
|
||||||
|
}
|
||||||
|
|
||||||
|
if deleted > 0 {
|
||||||
|
if len(changeStr) > 0 {
|
||||||
|
changeStr += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStr += red(fmt.Sprintf("-%d", deleted))
|
||||||
|
}
|
||||||
|
|
||||||
|
if whitelisted {
|
||||||
|
if len(changeStr) > 0 {
|
||||||
|
changeStr += " "
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStr += red("[whitelisted]")
|
||||||
|
}
|
||||||
|
|
||||||
|
if changeStr == "" {
|
||||||
|
changeStr = yellow("unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res {
|
||||||
|
fmt.Printf("\t%s\t%s %s %s (%s)\n", presep, sep, emoji.GreenCircle, parser, changeStr)
|
||||||
|
|
||||||
|
if opts.Details {
|
||||||
|
fmt.Print(detailsDisplay)
|
||||||
|
}
|
||||||
|
} else if opts.ShowNotOkParsers {
|
||||||
|
fmt.Printf("\t%s\t%s %s %s\n", presep, sep, emoji.RedCircle, parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sep := "└"
|
||||||
|
|
||||||
|
if len(state[tstamp]["buckets"]) > 0 {
|
||||||
|
sep = "├"
|
||||||
|
}
|
||||||
|
|
||||||
|
//did the event enter the bucket pour phase ?
|
||||||
|
if _, ok := state[tstamp]["buckets"]["OK"]; ok {
|
||||||
|
fmt.Printf("\t%s-------- parser success %s\n", sep, emoji.GreenCircle)
|
||||||
|
} else if whitelistReason != "" {
|
||||||
|
fmt.Printf("\t%s-------- parser success, ignored by whitelist (%s) %s\n", sep, whitelistReason, emoji.GreenCircle)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\t%s-------- parser failure %s\n", sep, emoji.RedCircle)
|
||||||
|
}
|
||||||
|
|
||||||
|
//now print bucket info
|
||||||
|
if len(state[tstamp]["buckets"]) > 0 {
|
||||||
|
fmt.Printf("\t├ Scenarios\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
bnames := make([]string, 0, len(state[tstamp]["buckets"]))
|
||||||
|
|
||||||
|
for k := range state[tstamp]["buckets"] {
|
||||||
|
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
||||||
|
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
||||||
|
if k == "OK" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bnames = append(bnames, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(bnames)
|
||||||
|
|
||||||
|
for idx, bname := range bnames {
|
||||||
|
sep := "├"
|
||||||
|
if idx == len(bnames)-1 {
|
||||||
|
sep = "└"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\t\t%s %s %s\n", sep, emoji.GreenCircle, bname)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||||
|
|
||||||
|
@ -78,6 +79,7 @@ func smokeHandler(req *http.Request) *http.Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedIP := strings.Split(req.URL.Path, "/")[3]
|
requestedIP := strings.Split(req.URL.Path, "/")[3]
|
||||||
|
|
||||||
sample, ok := sampledata[requestedIP]
|
sample, ok := sampledata[requestedIP]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
|
@ -109,9 +111,11 @@ func smokeHandler(req *http.Request) *http.Response {
|
||||||
|
|
||||||
func TestNillClient(t *testing.T) {
|
func TestNillClient(t *testing.T) {
|
||||||
defer ShutdownCrowdsecCTI()
|
defer ShutdownCrowdsecCTI()
|
||||||
|
|
||||||
if err := InitCrowdsecCTI(ptr.Of(""), nil, nil, nil); !errors.Is(err, cticlient.ErrDisabled) {
|
if err := InitCrowdsecCTI(ptr.Of(""), nil, nil, nil); !errors.Is(err, cticlient.ErrDisabled) {
|
||||||
t.Fatalf("failed to init CTI : %s", err)
|
t.Fatalf("failed to init CTI : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := CrowdsecCTI("1.2.3.4")
|
item, err := CrowdsecCTI("1.2.3.4")
|
||||||
assert.Equal(t, err, cticlient.ErrDisabled)
|
assert.Equal(t, err, cticlient.ErrDisabled)
|
||||||
assert.Equal(t, item, &cticlient.SmokeItem{})
|
assert.Equal(t, item, &cticlient.SmokeItem{})
|
||||||
|
@ -119,6 +123,7 @@ func TestNillClient(t *testing.T) {
|
||||||
|
|
||||||
func TestInvalidAuth(t *testing.T) {
|
func TestInvalidAuth(t *testing.T) {
|
||||||
defer ShutdownCrowdsecCTI()
|
defer ShutdownCrowdsecCTI()
|
||||||
|
|
||||||
if err := InitCrowdsecCTI(ptr.Of("asdasd"), nil, nil, nil); err != nil {
|
if err := InitCrowdsecCTI(ptr.Of("asdasd"), nil, nil, nil); err != nil {
|
||||||
t.Fatalf("failed to init CTI : %s", err)
|
t.Fatalf("failed to init CTI : %s", err)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +134,7 @@ func TestInvalidAuth(t *testing.T) {
|
||||||
|
|
||||||
item, err := CrowdsecCTI("1.2.3.4")
|
item, err := CrowdsecCTI("1.2.3.4")
|
||||||
assert.Equal(t, item, &cticlient.SmokeItem{})
|
assert.Equal(t, item, &cticlient.SmokeItem{})
|
||||||
assert.Equal(t, CTIApiEnabled, false)
|
assert.False(t, CTIApiEnabled)
|
||||||
assert.Equal(t, err, cticlient.ErrUnauthorized)
|
assert.Equal(t, err, cticlient.ErrUnauthorized)
|
||||||
|
|
||||||
//CTI is now disabled, all requests should return empty
|
//CTI is now disabled, all requests should return empty
|
||||||
|
@ -139,14 +144,15 @@ func TestInvalidAuth(t *testing.T) {
|
||||||
|
|
||||||
item, err = CrowdsecCTI("1.2.3.4")
|
item, err = CrowdsecCTI("1.2.3.4")
|
||||||
assert.Equal(t, item, &cticlient.SmokeItem{})
|
assert.Equal(t, item, &cticlient.SmokeItem{})
|
||||||
assert.Equal(t, CTIApiEnabled, false)
|
assert.False(t, CTIApiEnabled)
|
||||||
assert.Equal(t, err, cticlient.ErrDisabled)
|
assert.Equal(t, err, cticlient.ErrDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoKey(t *testing.T) {
|
func TestNoKey(t *testing.T) {
|
||||||
defer ShutdownCrowdsecCTI()
|
defer ShutdownCrowdsecCTI()
|
||||||
|
|
||||||
err := InitCrowdsecCTI(nil, nil, nil, nil)
|
err := InitCrowdsecCTI(nil, nil, nil, nil)
|
||||||
assert.ErrorIs(t, err, cticlient.ErrDisabled)
|
require.ErrorIs(t, err, cticlient.ErrDisabled)
|
||||||
//Replace the client created by InitCrowdsecCTI with one that uses a custom transport
|
//Replace the client created by InitCrowdsecCTI with one that uses a custom transport
|
||||||
ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{
|
ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{
|
||||||
Transport: RoundTripFunc(smokeHandler),
|
Transport: RoundTripFunc(smokeHandler),
|
||||||
|
@ -154,12 +160,13 @@ func TestNoKey(t *testing.T) {
|
||||||
|
|
||||||
item, err := CrowdsecCTI("1.2.3.4")
|
item, err := CrowdsecCTI("1.2.3.4")
|
||||||
assert.Equal(t, item, &cticlient.SmokeItem{})
|
assert.Equal(t, item, &cticlient.SmokeItem{})
|
||||||
assert.Equal(t, CTIApiEnabled, false)
|
assert.False(t, CTIApiEnabled)
|
||||||
assert.Equal(t, err, cticlient.ErrDisabled)
|
assert.Equal(t, err, cticlient.ErrDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
defer ShutdownCrowdsecCTI()
|
defer ShutdownCrowdsecCTI()
|
||||||
|
|
||||||
cacheDuration := 1 * time.Second
|
cacheDuration := 1 * time.Second
|
||||||
if err := InitCrowdsecCTI(ptr.Of(validApiKey), &cacheDuration, nil, nil); err != nil {
|
if err := InitCrowdsecCTI(ptr.Of(validApiKey), &cacheDuration, nil, nil); err != nil {
|
||||||
t.Fatalf("failed to init CTI : %s", err)
|
t.Fatalf("failed to init CTI : %s", err)
|
||||||
|
@ -172,28 +179,27 @@ func TestCache(t *testing.T) {
|
||||||
item, err := CrowdsecCTI("1.2.3.4")
|
item, err := CrowdsecCTI("1.2.3.4")
|
||||||
ctiResp := item.(*cticlient.SmokeItem)
|
ctiResp := item.(*cticlient.SmokeItem)
|
||||||
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
||||||
assert.Equal(t, CTIApiEnabled, true)
|
assert.True(t, CTIApiEnabled)
|
||||||
assert.Equal(t, CTICache.Len(true), 1)
|
assert.Equal(t, 1, CTICache.Len(true))
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
item, err = CrowdsecCTI("1.2.3.4")
|
item, err = CrowdsecCTI("1.2.3.4")
|
||||||
ctiResp = item.(*cticlient.SmokeItem)
|
ctiResp = item.(*cticlient.SmokeItem)
|
||||||
|
|
||||||
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
||||||
assert.Equal(t, CTIApiEnabled, true)
|
assert.True(t, CTIApiEnabled)
|
||||||
assert.Equal(t, CTICache.Len(true), 1)
|
assert.Equal(t, 1, CTICache.Len(true))
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
assert.Equal(t, CTICache.Len(true), 0)
|
assert.Equal(t, 0, CTICache.Len(true))
|
||||||
|
|
||||||
item, err = CrowdsecCTI("1.2.3.4")
|
item, err = CrowdsecCTI("1.2.3.4")
|
||||||
ctiResp = item.(*cticlient.SmokeItem)
|
ctiResp = item.(*cticlient.SmokeItem)
|
||||||
|
|
||||||
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
assert.Equal(t, "1.2.3.4", ctiResp.Ip)
|
||||||
assert.Equal(t, CTIApiEnabled, true)
|
assert.True(t, CTIApiEnabled)
|
||||||
assert.Equal(t, CTICache.Len(true), 1)
|
assert.Equal(t, 1, CTICache.Len(true))
|
||||||
assert.Equal(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,18 @@ var (
|
||||||
|
|
||||||
func getDBClient(t *testing.T) *database.Client {
|
func getDBClient(t *testing.T) *database.Client {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
dbPath, err := os.CreateTemp("", "*sqlite")
|
dbPath, err := os.CreateTemp("", "*sqlite")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testDbClient, err := database.NewClient(&csconfig.DatabaseCfg{
|
testDBClient, err := database.NewClient(&csconfig.DatabaseCfg{
|
||||||
Type: "sqlite",
|
Type: "sqlite",
|
||||||
DbName: "crowdsec",
|
DbName: "crowdsec",
|
||||||
DbPath: dbPath.Name(),
|
DbPath: dbPath.Name(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return testDbClient
|
return testDBClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVisitor(t *testing.T) {
|
func TestVisitor(t *testing.T) {
|
||||||
|
@ -109,17 +110,18 @@ func TestVisitor(t *testing.T) {
|
||||||
if err != nil && test.err == nil {
|
if err != nil && test.err == nil {
|
||||||
log.Fatalf("run : %s", err)
|
log.Fatalf("run : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isOk := assert.Equal(t, test.result, result); !isOk {
|
if isOk := assert.Equal(t, test.result, result); !isOk {
|
||||||
t.Fatalf("test '%s' : NOK", test.filter)
|
t.Fatalf("test '%s' : NOK", test.filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
func TestMatch(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
glob string
|
glob string
|
||||||
val string
|
val string
|
||||||
|
@ -149,12 +151,15 @@ func TestMatch(t *testing.T) {
|
||||||
"pattern": test.glob,
|
"pattern": test.glob,
|
||||||
"name": test.val,
|
"name": test.val,
|
||||||
}
|
}
|
||||||
|
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("pattern:%s val:%s NOK %s", test.glob, test.val, err)
|
t.Fatalf("pattern:%s val:%s NOK %s", test.glob, test.val, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err := expr.Run(vm, env)
|
ret, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if isOk := assert.Equal(t, test.ret, ret); !isOk {
|
if isOk := assert.Equal(t, test.ret, ret); !isOk {
|
||||||
t.Fatalf("pattern:%s val:%s NOK %t != %t", test.glob, test.val, ret, test.ret)
|
t.Fatalf("pattern:%s val:%s NOK %t != %t", test.glob, test.val, ret, test.ret)
|
||||||
}
|
}
|
||||||
|
@ -194,10 +199,10 @@ func TestDistanceHelper(t *testing.T) {
|
||||||
}
|
}
|
||||||
ret, err := expr.Run(vm, env)
|
ret, err := expr.Run(vm, env)
|
||||||
if test.valid {
|
if test.valid {
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.dist, ret)
|
assert.Equal(t, test.dist, ret)
|
||||||
} else {
|
} else {
|
||||||
assert.NotNil(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -283,10 +288,12 @@ func TestRegexpInFile(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := expr.Run(compiledFilter, map[string]interface{}{})
|
result, err := expr.Run(compiledFilter, map[string]interface{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isOk := assert.Equal(t, test.result, result); !isOk {
|
if isOk := assert.Equal(t, test.result, result); !isOk {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
|
@ -335,28 +342,34 @@ func TestFileInit(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if test.types == "string" {
|
|
||||||
|
switch test.types {
|
||||||
|
case "string":
|
||||||
if _, ok := dataFile[test.filename]; !ok {
|
if _, ok := dataFile[test.filename]; !ok {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
if isOk := assert.Equal(t, test.result, len(dataFile[test.filename])); !isOk {
|
|
||||||
|
if isOk := assert.Len(t, dataFile[test.filename], test.result); !isOk {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
} else if test.types == "regex" {
|
case "regex":
|
||||||
if _, ok := dataFileRegex[test.filename]; !ok {
|
if _, ok := dataFileRegex[test.filename]; !ok {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
if isOk := assert.Equal(t, test.result, len(dataFileRegex[test.filename])); !isOk {
|
|
||||||
|
if isOk := assert.Len(t, dataFileRegex[test.filename], test.result); !isOk {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
if _, ok := dataFileRegex[test.filename]; ok {
|
if _, ok := dataFileRegex[test.filename]; ok {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := dataFile[test.filename]; ok {
|
if _, ok := dataFile[test.filename]; ok {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("test '%s' : OK", test.name)
|
log.Printf("test '%s' : OK", test.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,21 +421,23 @@ func TestFile(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := expr.Run(compiledFilter, map[string]interface{}{})
|
result, err := expr.Run(compiledFilter, map[string]interface{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isOk := assert.Equal(t, test.result, result); !isOk {
|
if isOk := assert.Equal(t, test.result, result); !isOk {
|
||||||
t.Fatalf("test '%s' : NOK", test.name)
|
t.Fatalf("test '%s' : NOK", test.name)
|
||||||
}
|
}
|
||||||
log.Printf("test '%s' : OK", test.name)
|
|
||||||
|
|
||||||
|
log.Printf("test '%s' : OK", test.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIpInRange(t *testing.T) {
|
func TestIpInRange(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
env map[string]interface{}
|
env map[string]interface{}
|
||||||
|
@ -470,12 +485,11 @@ func TestIpInRange(t *testing.T) {
|
||||||
require.Equal(t, test.result, output)
|
require.Equal(t, test.result, output)
|
||||||
log.Printf("test '%s' : OK", test.name)
|
log.Printf("test '%s' : OK", test.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIpToRange(t *testing.T) {
|
func TestIpToRange(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
env map[string]interface{}
|
env map[string]interface{}
|
||||||
|
@ -543,13 +557,11 @@ func TestIpToRange(t *testing.T) {
|
||||||
require.Equal(t, test.result, output)
|
require.Equal(t, test.result, output)
|
||||||
log.Printf("test '%s' : OK", test.name)
|
log.Printf("test '%s' : OK", test.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtof(t *testing.T) {
|
func TestAtof(t *testing.T) {
|
||||||
|
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -593,13 +605,14 @@ func TestUpper(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
vm, err := expr.Compile("Upper(testStr)", GetExprOptions(env)...)
|
vm, err := expr.Compile("Upper(testStr)", GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
v, ok := out.(string)
|
v, ok := out.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Upper() should return a string")
|
t.Fatalf("Upper() should return a string")
|
||||||
|
@ -612,6 +625,7 @@ func TestUpper(t *testing.T) {
|
||||||
|
|
||||||
func TestTimeNow(t *testing.T) {
|
func TestTimeNow(t *testing.T) {
|
||||||
now, _ := TimeNow()
|
now, _ := TimeNow()
|
||||||
|
|
||||||
ti, err := time.Parse(time.RFC3339, now.(string))
|
ti, err := time.Parse(time.RFC3339, now.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error parsing the return value of TimeNow: %s", err)
|
t.Fatalf("Error parsing the return value of TimeNow: %s", err)
|
||||||
|
@ -620,6 +634,7 @@ func TestTimeNow(t *testing.T) {
|
||||||
if -1*time.Until(ti) > time.Second {
|
if -1*time.Until(ti) > time.Second {
|
||||||
t.Fatalf("TimeNow func should return time.Now().UTC()")
|
t.Fatalf("TimeNow func should return time.Now().UTC()")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("test 'TimeNow()' : OK")
|
log.Printf("test 'TimeNow()' : OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,15 +909,14 @@ func TestLower(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDecisionsCount(t *testing.T) {
|
func TestGetDecisionsCount(t *testing.T) {
|
||||||
var err error
|
|
||||||
var start_ip, start_sfx, end_ip, end_sfx int64
|
|
||||||
var ip_sz int
|
|
||||||
existingIP := "1.2.3.4"
|
existingIP := "1.2.3.4"
|
||||||
unknownIP := "1.2.3.5"
|
unknownIP := "1.2.3.5"
|
||||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(existingIP)
|
|
||||||
|
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
|
t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sample data to DB
|
// Add sample data to DB
|
||||||
dbClient = getDBClient(t)
|
dbClient = getDBClient(t)
|
||||||
|
|
||||||
|
@ -921,11 +935,11 @@ func TestGetDecisionsCount(t *testing.T) {
|
||||||
SaveX(context.Background())
|
SaveX(context.Background())
|
||||||
|
|
||||||
if decision == nil {
|
if decision == nil {
|
||||||
assert.Error(t, errors.Errorf("Failed to create sample decision"))
|
require.Error(t, errors.Errorf("Failed to create sample decision"))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Init(dbClient)
|
err = Init(dbClient)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -982,12 +996,10 @@ func TestGetDecisionsCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestGetDecisionsSinceCount(t *testing.T) {
|
func TestGetDecisionsSinceCount(t *testing.T) {
|
||||||
var err error
|
|
||||||
var start_ip, start_sfx, end_ip, end_sfx int64
|
|
||||||
var ip_sz int
|
|
||||||
existingIP := "1.2.3.4"
|
existingIP := "1.2.3.4"
|
||||||
unknownIP := "1.2.3.5"
|
unknownIP := "1.2.3.5"
|
||||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(existingIP)
|
|
||||||
|
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
|
t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
|
||||||
}
|
}
|
||||||
|
@ -1008,8 +1020,9 @@ func TestGetDecisionsSinceCount(t *testing.T) {
|
||||||
SetOrigin("CAPI").
|
SetOrigin("CAPI").
|
||||||
SaveX(context.Background())
|
SaveX(context.Background())
|
||||||
if decision == nil {
|
if decision == nil {
|
||||||
assert.Error(t, errors.Errorf("Failed to create sample decision"))
|
require.Error(t, errors.Errorf("Failed to create sample decision"))
|
||||||
}
|
}
|
||||||
|
|
||||||
decision2 := dbClient.Ent.Decision.Create().
|
decision2 := dbClient.Ent.Decision.Create().
|
||||||
SetCreatedAt(time.Now().AddDate(0, 0, -1)).
|
SetCreatedAt(time.Now().AddDate(0, 0, -1)).
|
||||||
SetUntil(time.Now().AddDate(0, 0, -1)).
|
SetUntil(time.Now().AddDate(0, 0, -1)).
|
||||||
|
@ -1024,12 +1037,13 @@ func TestGetDecisionsSinceCount(t *testing.T) {
|
||||||
SetValue(existingIP).
|
SetValue(existingIP).
|
||||||
SetOrigin("CAPI").
|
SetOrigin("CAPI").
|
||||||
SaveX(context.Background())
|
SaveX(context.Background())
|
||||||
|
|
||||||
if decision2 == nil {
|
if decision2 == nil {
|
||||||
assert.Error(t, errors.Errorf("Failed to create sample decision"))
|
require.Error(t, errors.Errorf("Failed to create sample decision"))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Init(dbClient)
|
err = Init(dbClient)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -1152,6 +1166,7 @@ func TestIsIp(t *testing.T) {
|
||||||
if err := Init(nil); err != nil {
|
if err := Init(nil); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expr string
|
expr string
|
||||||
|
@ -1235,17 +1250,18 @@ func TestIsIp(t *testing.T) {
|
||||||
expectedBuildErr: true,
|
expectedBuildErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
||||||
if tc.expectedBuildErr {
|
if tc.expectedBuildErr {
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.IsType(t, tc.expected, output)
|
assert.IsType(t, tc.expected, output)
|
||||||
assert.Equal(t, tc.expected, output.(bool))
|
assert.Equal(t, tc.expected, output.(bool))
|
||||||
})
|
})
|
||||||
|
@ -1255,6 +1271,7 @@ func TestIsIp(t *testing.T) {
|
||||||
func TestToString(t *testing.T) {
|
func TestToString(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
value interface{}
|
value interface{}
|
||||||
|
@ -1290,9 +1307,9 @@ func TestToString(t *testing.T) {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expected, output)
|
require.Equal(t, tc.expected, output)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1338,16 +1355,16 @@ func TestB64Decode(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
|
||||||
if tc.expectedBuildErr {
|
if tc.expectedBuildErr {
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
|
||||||
if tc.expectedRuntimeErr {
|
if tc.expectedRuntimeErr {
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expected, output)
|
require.Equal(t, tc.expected, output)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1412,9 +1429,9 @@ func TestParseKv(t *testing.T) {
|
||||||
"out": outMap,
|
"out": outMap,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(tc.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(tc.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = expr.Run(vm, env)
|
_, err = expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tc.expected, outMap["a"])
|
assert.Equal(t, tc.expected, outMap["a"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/antonmedv/expr"
|
"github.com/antonmedv/expr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJsonExtract(t *testing.T) {
|
func TestJsonExtract(t *testing.T) {
|
||||||
|
@ -56,14 +57,14 @@ func TestJsonExtract(t *testing.T) {
|
||||||
"target": test.targetField,
|
"target": test.targetField,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, out)
|
assert.Equal(t, test.expectResult, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJsonExtractUnescape(t *testing.T) {
|
func TestJsonExtractUnescape(t *testing.T) {
|
||||||
if err := Init(nil); err != nil {
|
if err := Init(nil); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -104,9 +105,9 @@ func TestJsonExtractUnescape(t *testing.T) {
|
||||||
"target": test.targetField,
|
"target": test.targetField,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, out)
|
assert.Equal(t, test.expectResult, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -167,9 +168,9 @@ func TestJsonExtractSlice(t *testing.T) {
|
||||||
"target": test.targetField,
|
"target": test.targetField,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, out)
|
assert.Equal(t, test.expectResult, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -223,9 +224,9 @@ func TestJsonExtractObject(t *testing.T) {
|
||||||
"target": test.targetField,
|
"target": test.targetField,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, out)
|
assert.Equal(t, test.expectResult, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -233,7 +234,8 @@ func TestJsonExtractObject(t *testing.T) {
|
||||||
|
|
||||||
func TestToJson(t *testing.T) {
|
func TestToJson(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
obj interface{}
|
obj interface{}
|
||||||
|
@ -298,9 +300,9 @@ func TestToJson(t *testing.T) {
|
||||||
"obj": test.obj,
|
"obj": test.obj,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
out, err := expr.Run(vm, env)
|
out, err := expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, out)
|
assert.Equal(t, test.expectResult, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -308,7 +310,8 @@ func TestToJson(t *testing.T) {
|
||||||
|
|
||||||
func TestUnmarshalJSON(t *testing.T) {
|
func TestUnmarshalJSON(t *testing.T) {
|
||||||
err := Init(nil)
|
err := Init(nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
json string
|
json string
|
||||||
|
@ -361,11 +364,10 @@ func TestUnmarshalJSON(t *testing.T) {
|
||||||
"out": outMap,
|
"out": outMap,
|
||||||
}
|
}
|
||||||
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = expr.Run(vm, env)
|
_, err = expr.Run(vm, env)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expectResult, outMap["a"])
|
assert.Equal(t, test.expectResult, outMap["a"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
|
"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
|
"github.com/crowdsecurity/go-cs-lib/maptools"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +26,7 @@ func (h *HubTest) GetAppsecCoverage() ([]Coverage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate from hub, iterate in alphabetical order
|
// populate from hub, iterate in alphabetical order
|
||||||
pkeys := sortedMapKeys(h.HubIndex.GetItemMap(cwhub.APPSEC_RULES))
|
pkeys := maptools.SortedKeys(h.HubIndex.GetItemMap(cwhub.APPSEC_RULES))
|
||||||
coverage := make([]Coverage, len(pkeys))
|
coverage := make([]Coverage, len(pkeys))
|
||||||
|
|
||||||
for i, name := range pkeys {
|
for i, name := range pkeys {
|
||||||
|
@ -84,7 +85,7 @@ func (h *HubTest) GetParsersCoverage() ([]Coverage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate from hub, iterate in alphabetical order
|
// populate from hub, iterate in alphabetical order
|
||||||
pkeys := sortedMapKeys(h.HubIndex.GetItemMap(cwhub.PARSERS))
|
pkeys := maptools.SortedKeys(h.HubIndex.GetItemMap(cwhub.PARSERS))
|
||||||
coverage := make([]Coverage, len(pkeys))
|
coverage := make([]Coverage, len(pkeys))
|
||||||
|
|
||||||
for i, name := range pkeys {
|
for i, name := range pkeys {
|
||||||
|
@ -170,7 +171,7 @@ func (h *HubTest) GetScenariosCoverage() ([]Coverage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate from hub, iterate in alphabetical order
|
// populate from hub, iterate in alphabetical order
|
||||||
pkeys := sortedMapKeys(h.HubIndex.GetItemMap(cwhub.SCENARIOS))
|
pkeys := maptools.SortedKeys(h.HubIndex.GetItemMap(cwhub.SCENARIOS))
|
||||||
coverage := make([]Coverage, len(pkeys))
|
coverage := make([]Coverage, len(pkeys))
|
||||||
|
|
||||||
for i, name := range pkeys {
|
for i, name := range pkeys {
|
||||||
|
|
|
@ -3,21 +3,16 @@ package hubtest
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/antonmedv/expr"
|
"github.com/antonmedv/expr"
|
||||||
"github.com/enescakir/emoji"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
diff "github.com/r3labs/diff/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/dumps"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/go-cs-lib/maptools"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AssertFail struct {
|
type AssertFail struct {
|
||||||
|
@ -34,16 +29,9 @@ type ParserAssert struct {
|
||||||
NbAssert int
|
NbAssert int
|
||||||
Fails []AssertFail
|
Fails []AssertFail
|
||||||
Success bool
|
Success bool
|
||||||
TestData *ParserResults
|
TestData *dumps.ParserResults
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParserResult struct {
|
|
||||||
Evt types.Event
|
|
||||||
Success bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParserResults map[string]map[string][]ParserResult
|
|
||||||
|
|
||||||
func NewParserAssert(file string) *ParserAssert {
|
func NewParserAssert(file string) *ParserAssert {
|
||||||
ParserAssert := &ParserAssert{
|
ParserAssert := &ParserAssert{
|
||||||
File: file,
|
File: file,
|
||||||
|
@ -51,7 +39,7 @@ func NewParserAssert(file string) *ParserAssert {
|
||||||
Success: false,
|
Success: false,
|
||||||
Fails: make([]AssertFail, 0),
|
Fails: make([]AssertFail, 0),
|
||||||
AutoGenAssert: false,
|
AutoGenAssert: false,
|
||||||
TestData: &ParserResults{},
|
TestData: &dumps.ParserResults{},
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParserAssert
|
return ParserAssert
|
||||||
|
@ -69,7 +57,7 @@ func (p *ParserAssert) AutoGenFromFile(filename string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ParserAssert) LoadTest(filename string) error {
|
func (p *ParserAssert) LoadTest(filename string) error {
|
||||||
parserDump, err := LoadParserDump(filename)
|
parserDump, err := dumps.LoadParserDump(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading parser dump file: %+v", err)
|
return fmt.Errorf("loading parser dump file: %+v", err)
|
||||||
}
|
}
|
||||||
|
@ -229,13 +217,13 @@ func (p *ParserAssert) AutoGenParserAssert() string {
|
||||||
ret := fmt.Sprintf("len(results) == %d\n", len(*p.TestData))
|
ret := fmt.Sprintf("len(results) == %d\n", len(*p.TestData))
|
||||||
|
|
||||||
//sort map keys for consistent order
|
//sort map keys for consistent order
|
||||||
stages := sortedMapKeys(*p.TestData)
|
stages := maptools.SortedKeys(*p.TestData)
|
||||||
|
|
||||||
for _, stage := range stages {
|
for _, stage := range stages {
|
||||||
parsers := (*p.TestData)[stage]
|
parsers := (*p.TestData)[stage]
|
||||||
|
|
||||||
//sort map keys for consistent order
|
//sort map keys for consistent order
|
||||||
pnames := sortedMapKeys(parsers)
|
pnames := maptools.SortedKeys(parsers)
|
||||||
|
|
||||||
for _, parser := range pnames {
|
for _, parser := range pnames {
|
||||||
presults := parsers[parser]
|
presults := parsers[parser]
|
||||||
|
@ -248,7 +236,7 @@ func (p *ParserAssert) AutoGenParserAssert() string {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkey := range sortedMapKeys(result.Evt.Parsed) {
|
for _, pkey := range maptools.SortedKeys(result.Evt.Parsed) {
|
||||||
pval := result.Evt.Parsed[pkey]
|
pval := result.Evt.Parsed[pkey]
|
||||||
if pval == "" {
|
if pval == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -257,7 +245,7 @@ func (p *ParserAssert) AutoGenParserAssert() string {
|
||||||
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s"`+"\n", stage, parser, pidx, pkey, Escape(pval))
|
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s"`+"\n", stage, parser, pidx, pkey, Escape(pval))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mkey := range sortedMapKeys(result.Evt.Meta) {
|
for _, mkey := range maptools.SortedKeys(result.Evt.Meta) {
|
||||||
mval := result.Evt.Meta[mkey]
|
mval := result.Evt.Meta[mkey]
|
||||||
if mval == "" {
|
if mval == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -266,7 +254,7 @@ func (p *ParserAssert) AutoGenParserAssert() string {
|
||||||
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s"`+"\n", stage, parser, pidx, mkey, Escape(mval))
|
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s"`+"\n", stage, parser, pidx, mkey, Escape(mval))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ekey := range sortedMapKeys(result.Evt.Enriched) {
|
for _, ekey := range maptools.SortedKeys(result.Evt.Enriched) {
|
||||||
eval := result.Evt.Enriched[ekey]
|
eval := result.Evt.Enriched[ekey]
|
||||||
if eval == "" {
|
if eval == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -275,7 +263,7 @@ func (p *ParserAssert) AutoGenParserAssert() string {
|
||||||
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s"`+"\n", stage, parser, pidx, ekey, Escape(eval))
|
ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s"`+"\n", stage, parser, pidx, ekey, Escape(eval))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ukey := range sortedMapKeys(result.Evt.Unmarshaled) {
|
for _, ukey := range maptools.SortedKeys(result.Evt.Unmarshaled) {
|
||||||
uval := result.Evt.Unmarshaled[ukey]
|
uval := result.Evt.Unmarshaled[ukey]
|
||||||
if uval == "" {
|
if uval == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -328,288 +316,3 @@ func (p *ParserAssert) buildUnmarshaledAssert(ekey string, eval interface{}) []s
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadParserDump(filepath string) (*ParserResults, error) {
|
|
||||||
dumpData, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer dumpData.Close()
|
|
||||||
|
|
||||||
results, err := io.ReadAll(dumpData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pdump := ParserResults{}
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(results, &pdump); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we know that some variables should always be set,
|
|
||||||
let's check if they're present in last parser output of last stage */
|
|
||||||
|
|
||||||
stages := sortedMapKeys(pdump)
|
|
||||||
|
|
||||||
var lastStage string
|
|
||||||
|
|
||||||
//Loop over stages to find last successful one with at least one parser
|
|
||||||
for i := len(stages) - 2; i >= 0; i-- {
|
|
||||||
if len(pdump[stages[i]]) != 0 {
|
|
||||||
lastStage = stages[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsers := make([]string, 0, len(pdump[lastStage]))
|
|
||||||
|
|
||||||
for k := range pdump[lastStage] {
|
|
||||||
parsers = append(parsers, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(parsers)
|
|
||||||
|
|
||||||
if len(parsers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no parser found. Please install the appropriate parser and retry")
|
|
||||||
}
|
|
||||||
|
|
||||||
lastParser := parsers[len(parsers)-1]
|
|
||||||
|
|
||||||
for idx, result := range pdump[lastStage][lastParser] {
|
|
||||||
if result.Evt.StrTime == "" {
|
|
||||||
log.Warningf("Line %d/%d is missing evt.StrTime. It is most likely a mistake as it will prevent your logs to be processed in time-machine/forensic mode.", idx, len(pdump[lastStage][lastParser]))
|
|
||||||
} else {
|
|
||||||
log.Debugf("Line %d/%d has evt.StrTime set to '%s'", idx, len(pdump[lastStage][lastParser]), result.Evt.StrTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pdump, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DumpOpts struct {
|
|
||||||
Details bool
|
|
||||||
SkipOk bool
|
|
||||||
ShowNotOkParsers bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func DumpTree(parserResults ParserResults, bucketPour BucketPourInfo, opts DumpOpts) {
|
|
||||||
//note : we can use line -> time as the unique identifier (of acquisition)
|
|
||||||
state := make(map[time.Time]map[string]map[string]ParserResult)
|
|
||||||
assoc := make(map[time.Time]string, 0)
|
|
||||||
|
|
||||||
for stage, parsers := range parserResults {
|
|
||||||
for parser, results := range parsers {
|
|
||||||
for _, parserRes := range results {
|
|
||||||
evt := parserRes.Evt
|
|
||||||
if _, ok := state[evt.Line.Time]; !ok {
|
|
||||||
state[evt.Line.Time] = make(map[string]map[string]ParserResult)
|
|
||||||
assoc[evt.Line.Time] = evt.Line.Raw
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state[evt.Line.Time][stage]; !ok {
|
|
||||||
state[evt.Line.Time][stage] = make(map[string]ParserResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
state[evt.Line.Time][stage][parser] = ParserResult{Evt: evt, Success: parserRes.Success}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for bname, evtlist := range bucketPour {
|
|
||||||
for _, evt := range evtlist {
|
|
||||||
if evt.Line.Raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//it might be bucket overflow being reprocessed, skip this
|
|
||||||
if _, ok := state[evt.Line.Time]; !ok {
|
|
||||||
state[evt.Line.Time] = make(map[string]map[string]ParserResult)
|
|
||||||
assoc[evt.Line.Time] = evt.Line.Raw
|
|
||||||
}
|
|
||||||
|
|
||||||
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
|
||||||
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
|
||||||
if _, ok := state[evt.Line.Time]["buckets"]; !ok {
|
|
||||||
state[evt.Line.Time]["buckets"] = make(map[string]ParserResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
state[evt.Line.Time]["buckets"][bname] = ParserResult{Success: true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yellow := color.New(color.FgYellow).SprintFunc()
|
|
||||||
red := color.New(color.FgRed).SprintFunc()
|
|
||||||
green := color.New(color.FgGreen).SprintFunc()
|
|
||||||
whitelistReason := ""
|
|
||||||
//get each line
|
|
||||||
for tstamp, rawstr := range assoc {
|
|
||||||
if opts.SkipOk {
|
|
||||||
if _, ok := state[tstamp]["buckets"]["OK"]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("line: %s\n", rawstr)
|
|
||||||
|
|
||||||
skeys := make([]string, 0, len(state[tstamp]))
|
|
||||||
|
|
||||||
for k := range state[tstamp] {
|
|
||||||
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
|
||||||
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
|
||||||
if k == "buckets" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
skeys = append(skeys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(skeys)
|
|
||||||
|
|
||||||
// iterate stage
|
|
||||||
var prevItem types.Event
|
|
||||||
|
|
||||||
for _, stage := range skeys {
|
|
||||||
parsers := state[tstamp][stage]
|
|
||||||
|
|
||||||
sep := "├"
|
|
||||||
presep := "|"
|
|
||||||
|
|
||||||
fmt.Printf("\t%s %s\n", sep, stage)
|
|
||||||
|
|
||||||
pkeys := sortedMapKeys(parsers)
|
|
||||||
|
|
||||||
for idx, parser := range pkeys {
|
|
||||||
res := parsers[parser].Success
|
|
||||||
sep := "├"
|
|
||||||
|
|
||||||
if idx == len(pkeys)-1 {
|
|
||||||
sep = "└"
|
|
||||||
}
|
|
||||||
|
|
||||||
created := 0
|
|
||||||
updated := 0
|
|
||||||
deleted := 0
|
|
||||||
whitelisted := false
|
|
||||||
changeStr := ""
|
|
||||||
detailsDisplay := ""
|
|
||||||
|
|
||||||
if res {
|
|
||||||
changelog, _ := diff.Diff(prevItem, parsers[parser].Evt)
|
|
||||||
for _, change := range changelog {
|
|
||||||
switch change.Type {
|
|
||||||
case "create":
|
|
||||||
created++
|
|
||||||
|
|
||||||
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), green(change.To))
|
|
||||||
case "update":
|
|
||||||
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s -> %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), change.From, yellow(change.To))
|
|
||||||
|
|
||||||
if change.Path[0] == "Whitelisted" && change.To == true {
|
|
||||||
whitelisted = true
|
|
||||||
|
|
||||||
if whitelistReason == "" {
|
|
||||||
whitelistReason = parsers[parser].Evt.WhitelistReason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updated++
|
|
||||||
case "delete":
|
|
||||||
deleted++
|
|
||||||
|
|
||||||
detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s\n", presep, sep, change.Type, red(strings.Join(change.Path, ".")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevItem = parsers[parser].Evt
|
|
||||||
}
|
|
||||||
|
|
||||||
if created > 0 {
|
|
||||||
changeStr += green(fmt.Sprintf("+%d", created))
|
|
||||||
}
|
|
||||||
|
|
||||||
if updated > 0 {
|
|
||||||
if len(changeStr) > 0 {
|
|
||||||
changeStr += " "
|
|
||||||
}
|
|
||||||
|
|
||||||
changeStr += yellow(fmt.Sprintf("~%d", updated))
|
|
||||||
}
|
|
||||||
|
|
||||||
if deleted > 0 {
|
|
||||||
if len(changeStr) > 0 {
|
|
||||||
changeStr += " "
|
|
||||||
}
|
|
||||||
|
|
||||||
changeStr += red(fmt.Sprintf("-%d", deleted))
|
|
||||||
}
|
|
||||||
|
|
||||||
if whitelisted {
|
|
||||||
if len(changeStr) > 0 {
|
|
||||||
changeStr += " "
|
|
||||||
}
|
|
||||||
|
|
||||||
changeStr += red("[whitelisted]")
|
|
||||||
}
|
|
||||||
|
|
||||||
if changeStr == "" {
|
|
||||||
changeStr = yellow("unchanged")
|
|
||||||
}
|
|
||||||
|
|
||||||
if res {
|
|
||||||
fmt.Printf("\t%s\t%s %s %s (%s)\n", presep, sep, emoji.GreenCircle, parser, changeStr)
|
|
||||||
|
|
||||||
if opts.Details {
|
|
||||||
fmt.Print(detailsDisplay)
|
|
||||||
}
|
|
||||||
} else if opts.ShowNotOkParsers {
|
|
||||||
fmt.Printf("\t%s\t%s %s %s\n", presep, sep, emoji.RedCircle, parser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sep := "└"
|
|
||||||
|
|
||||||
if len(state[tstamp]["buckets"]) > 0 {
|
|
||||||
sep = "├"
|
|
||||||
}
|
|
||||||
|
|
||||||
//did the event enter the bucket pour phase ?
|
|
||||||
if _, ok := state[tstamp]["buckets"]["OK"]; ok {
|
|
||||||
fmt.Printf("\t%s-------- parser success %s\n", sep, emoji.GreenCircle)
|
|
||||||
} else if whitelistReason != "" {
|
|
||||||
fmt.Printf("\t%s-------- parser success, ignored by whitelist (%s) %s\n", sep, whitelistReason, emoji.GreenCircle)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\t%s-------- parser failure %s\n", sep, emoji.RedCircle)
|
|
||||||
}
|
|
||||||
|
|
||||||
//now print bucket info
|
|
||||||
if len(state[tstamp]["buckets"]) > 0 {
|
|
||||||
fmt.Printf("\t├ Scenarios\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
bnames := make([]string, 0, len(state[tstamp]["buckets"]))
|
|
||||||
|
|
||||||
for k := range state[tstamp]["buckets"] {
|
|
||||||
//there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
|
|
||||||
//we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
|
|
||||||
if k == "OK" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bnames = append(bnames, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(bnames)
|
|
||||||
|
|
||||||
for idx, bname := range bnames {
|
|
||||||
sep := "├"
|
|
||||||
if idx == len(bnames)-1 {
|
|
||||||
sep = "└"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\t\t%s %s %s\n", sep, emoji.GreenCircle, bname)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/dumps"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -24,11 +25,10 @@ type ScenarioAssert struct {
|
||||||
Fails []AssertFail
|
Fails []AssertFail
|
||||||
Success bool
|
Success bool
|
||||||
TestData *BucketResults
|
TestData *BucketResults
|
||||||
PourData *BucketPourInfo
|
PourData *dumps.BucketPourInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type BucketResults []types.Event
|
type BucketResults []types.Event
|
||||||
type BucketPourInfo map[string][]types.Event
|
|
||||||
|
|
||||||
func NewScenarioAssert(file string) *ScenarioAssert {
|
func NewScenarioAssert(file string) *ScenarioAssert {
|
||||||
ScenarioAssert := &ScenarioAssert{
|
ScenarioAssert := &ScenarioAssert{
|
||||||
|
@ -38,7 +38,7 @@ func NewScenarioAssert(file string) *ScenarioAssert {
|
||||||
Fails: make([]AssertFail, 0),
|
Fails: make([]AssertFail, 0),
|
||||||
AutoGenAssert: false,
|
AutoGenAssert: false,
|
||||||
TestData: &BucketResults{},
|
TestData: &BucketResults{},
|
||||||
PourData: &BucketPourInfo{},
|
PourData: &dumps.BucketPourInfo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScenarioAssert
|
return ScenarioAssert
|
||||||
|
@ -64,7 +64,7 @@ func (s *ScenarioAssert) LoadTest(filename string, bucketpour string) error {
|
||||||
s.TestData = bucketDump
|
s.TestData = bucketDump
|
||||||
|
|
||||||
if bucketpour != "" {
|
if bucketpour != "" {
|
||||||
pourDump, err := LoadBucketPourDump(bucketpour)
|
pourDump, err := dumps.LoadBucketPourDump(bucketpour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading bucket pour dump file '%s': %+v", filename, err)
|
return fmt.Errorf("loading bucket pour dump file '%s': %+v", filename, err)
|
||||||
}
|
}
|
||||||
|
@ -252,27 +252,6 @@ func (b BucketResults) Swap(i, j int) {
|
||||||
b[i], b[j] = b[j], b[i]
|
b[i], b[j] = b[j], b[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadBucketPourDump(filepath string) (*BucketPourInfo, error) {
|
|
||||||
dumpData, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer dumpData.Close()
|
|
||||||
|
|
||||||
results, err := io.ReadAll(dumpData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var bucketDump BucketPourInfo
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(results, &bucketDump); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bucketDump, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadScenarioDump(filepath string) (*BucketResults, error) {
|
func LoadScenarioDump(filepath string) (*BucketResults, error) {
|
||||||
dumpData, err := os.Open(filepath)
|
dumpData, err := os.Open(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,21 +5,25 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sortedMapKeys[V any](m map[string]V) []string {
|
func IsAlive(target string) (bool, error) {
|
||||||
keys := make([]string, 0, len(m))
|
start := time.Now()
|
||||||
for k := range m {
|
for {
|
||||||
keys = append(keys, k)
|
conn, err := net.Dial("tcp", target)
|
||||||
|
if err == nil {
|
||||||
|
log.Debugf("'%s' is up after %s", target, time.Since(start))
|
||||||
|
conn.Close()
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
if time.Since(start) > 10*time.Second {
|
||||||
|
return false, fmt.Errorf("took more than 10s for %s to be available", target)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
return keys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Copy(src string, dst string) error {
|
func Copy(src string, dst string) error {
|
||||||
|
@ -110,19 +114,3 @@ func CopyDir(src string, dest string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsAlive(target string) (bool, error) {
|
|
||||||
start := time.Now()
|
|
||||||
for {
|
|
||||||
conn, err := net.Dial("tcp", target)
|
|
||||||
if err == nil {
|
|
||||||
log.Debugf("'%s' is up after %s", target, time.Since(start))
|
|
||||||
conn.Close()
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
if time.Since(start) > 10*time.Second {
|
|
||||||
return false, fmt.Errorf("took more than 10s for %s to be available", target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/dumps"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -229,14 +230,10 @@ func stageidx(stage string, stages []string) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParserResult struct {
|
|
||||||
Evt types.Event
|
|
||||||
Success bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var ParseDump bool
|
var ParseDump bool
|
||||||
var DumpFolder string
|
var DumpFolder string
|
||||||
var StageParseCache map[string]map[string][]ParserResult
|
|
||||||
|
var StageParseCache dumps.ParserResults
|
||||||
var StageParseMutex sync.Mutex
|
var StageParseMutex sync.Mutex
|
||||||
|
|
||||||
func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error) {
|
func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error) {
|
||||||
|
@ -271,9 +268,9 @@ func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error)
|
||||||
if ParseDump {
|
if ParseDump {
|
||||||
if StageParseCache == nil {
|
if StageParseCache == nil {
|
||||||
StageParseMutex.Lock()
|
StageParseMutex.Lock()
|
||||||
StageParseCache = make(map[string]map[string][]ParserResult)
|
StageParseCache = make(dumps.ParserResults)
|
||||||
StageParseCache["success"] = make(map[string][]ParserResult)
|
StageParseCache["success"] = make(map[string][]dumps.ParserResult)
|
||||||
StageParseCache["success"][""] = make([]ParserResult, 0)
|
StageParseCache["success"][""] = make([]dumps.ParserResult, 0)
|
||||||
StageParseMutex.Unlock()
|
StageParseMutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +279,7 @@ func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error)
|
||||||
if ParseDump {
|
if ParseDump {
|
||||||
StageParseMutex.Lock()
|
StageParseMutex.Lock()
|
||||||
if _, ok := StageParseCache[stage]; !ok {
|
if _, ok := StageParseCache[stage]; !ok {
|
||||||
StageParseCache[stage] = make(map[string][]ParserResult)
|
StageParseCache[stage] = make(map[string][]dumps.ParserResult)
|
||||||
}
|
}
|
||||||
StageParseMutex.Unlock()
|
StageParseMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -322,13 +319,18 @@ func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error)
|
||||||
}
|
}
|
||||||
clog.Tracef("node (%s) ret : %v", node.rn, ret)
|
clog.Tracef("node (%s) ret : %v", node.rn, ret)
|
||||||
if ParseDump {
|
if ParseDump {
|
||||||
|
parserIdxInStage := 0
|
||||||
StageParseMutex.Lock()
|
StageParseMutex.Lock()
|
||||||
if len(StageParseCache[stage][node.Name]) == 0 {
|
if len(StageParseCache[stage][node.Name]) == 0 {
|
||||||
StageParseCache[stage][node.Name] = make([]ParserResult, 0)
|
StageParseCache[stage][node.Name] = make([]dumps.ParserResult, 0)
|
||||||
|
parserIdxInStage = len(StageParseCache[stage])
|
||||||
|
} else {
|
||||||
|
parserIdxInStage = StageParseCache[stage][node.Name][0].Idx
|
||||||
}
|
}
|
||||||
StageParseMutex.Unlock()
|
StageParseMutex.Unlock()
|
||||||
|
|
||||||
evtcopy := deepcopy.Copy(event)
|
evtcopy := deepcopy.Copy(event)
|
||||||
parserInfo := ParserResult{Evt: evtcopy.(types.Event), Success: ret}
|
parserInfo := dumps.ParserResult{Evt: evtcopy.(types.Event), Success: ret, Idx: parserIdxInStage}
|
||||||
StageParseMutex.Lock()
|
StageParseMutex.Lock()
|
||||||
StageParseCache[stage][node.Name] = append(StageParseCache[stage][node.Name], parserInfo)
|
StageParseCache[stage][node.Name] = append(StageParseCache[stage][node.Name], parserInfo)
|
||||||
StageParseMutex.Unlock()
|
StageParseMutex.Unlock()
|
||||||
|
|
|
@ -353,7 +353,7 @@ func TestUnitFound(t *testing.T) {
|
||||||
installed, err := env.UnitFound("crowdsec-setup-detect.service")
|
installed, err := env.UnitFound("crowdsec-setup-detect.service")
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
require.Equal(true, installed)
|
require.True(installed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO apply rules to filter a list of Service structs
|
// TODO apply rules to filter a list of Service structs
|
||||||
|
@ -566,8 +566,8 @@ func TestDetectForcedUnit(t *testing.T) {
|
||||||
|
|
||||||
func TestDetectForcedProcess(t *testing.T) {
|
func TestDetectForcedProcess(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("skipping on windows")
|
|
||||||
// while looking for service wizard: rule 'ProcessRunning("foobar")': while looking up running processes: could not get Name: A device attached to the system is not functioning.
|
// while looking for service wizard: rule 'ProcessRunning("foobar")': while looking up running processes: could not get Name: A device attached to the system is not functioning.
|
||||||
|
t.Skip("skipping on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
|
@ -73,7 +73,7 @@ func TestParseIPSources(t *testing.T) {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ips := tt.evt.ParseIPSources()
|
ips := tt.evt.ParseIPSources()
|
||||||
assert.Equal(t, ips, tt.expected)
|
assert.Equal(t, tt.expected, ips)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ func SetDefaultLoggerConfig(cfgMode string, cfgFolder string, cfgLevel log.Level
|
||||||
}
|
}
|
||||||
logLevel = cfgLevel
|
logLevel = cfgLevel
|
||||||
log.SetLevel(logLevel)
|
log.SetLevel(logLevel)
|
||||||
logFormatter = &log.TextFormatter{TimestampFormat: "2006-01-02 15:04:05", FullTimestamp: true, ForceColors: forceColors}
|
logFormatter = &log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true, ForceColors: forceColors}
|
||||||
log.SetFormatter(logFormatter)
|
log.SetFormatter(logFormatter)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,35 @@ setup() {
|
||||||
|
|
||||||
#----------
|
#----------
|
||||||
|
|
||||||
|
@test "cscli capi status: fails without credentials" {
|
||||||
|
config_enable_capi
|
||||||
|
ONLINE_API_CREDENTIALS_YAML="$(config_get '.api.server.online_client.credentials_path')"
|
||||||
|
# bogus values, won't be used
|
||||||
|
echo '{"login":"login","password":"password","url":"url"}' > "${ONLINE_API_CREDENTIALS_YAML}"
|
||||||
|
|
||||||
|
config_set "$ONLINE_API_CREDENTIALS_YAML" 'del(.url)'
|
||||||
|
rune -1 cscli capi status
|
||||||
|
assert_stderr --partial "can't load CAPI credentials from '$ONLINE_API_CREDENTIALS_YAML' (missing url field)"
|
||||||
|
|
||||||
|
config_set "$ONLINE_API_CREDENTIALS_YAML" 'del(.password)'
|
||||||
|
rune -1 cscli capi status
|
||||||
|
assert_stderr --partial "can't load CAPI credentials from '$ONLINE_API_CREDENTIALS_YAML' (missing password field)"
|
||||||
|
|
||||||
|
config_set "$ONLINE_API_CREDENTIALS_YAML" 'del(.login)'
|
||||||
|
rune -1 cscli capi status
|
||||||
|
assert_stderr --partial "can't load CAPI credentials from '$ONLINE_API_CREDENTIALS_YAML' (missing login field)"
|
||||||
|
|
||||||
|
rm "${ONLINE_API_CREDENTIALS_YAML}"
|
||||||
|
rune -1 cscli capi status
|
||||||
|
assert_stderr --partial "failed to load Local API: loading online client credentials: open ${ONLINE_API_CREDENTIALS_YAML}: no such file or directory"
|
||||||
|
|
||||||
|
config_set 'del(.api.server.online_client)'
|
||||||
|
rune -1 cscli capi status
|
||||||
|
assert_stderr --partial "no configuration for Central API (CAPI) in '$CONFIG_YAML'"
|
||||||
|
}
|
||||||
|
|
||||||
@test "cscli capi status" {
|
@test "cscli capi status" {
|
||||||
|
./instance-data load
|
||||||
config_enable_capi
|
config_enable_capi
|
||||||
rune -0 cscli capi register --schmilblick githubciXXXXXXXXXXXXXXXXXXXXXXXX
|
rune -0 cscli capi register --schmilblick githubciXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
rune -1 cscli capi status
|
rune -1 cscli capi status
|
||||||
|
@ -60,13 +88,6 @@ setup() {
|
||||||
assert_stderr --partial "You can successfully interact with Central API (CAPI)"
|
assert_stderr --partial "You can successfully interact with Central API (CAPI)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cscli capi status: fails without credentials" {
|
|
||||||
ONLINE_API_CREDENTIALS_YAML="$(config_get '.api.server.online_client.credentials_path')"
|
|
||||||
rm "${ONLINE_API_CREDENTIALS_YAML}"
|
|
||||||
rune -1 cscli capi status
|
|
||||||
assert_stderr --partial "failed to load Local API: loading online client credentials: failed to read api server credentials configuration file '${ONLINE_API_CREDENTIALS_YAML}': open ${ONLINE_API_CREDENTIALS_YAML}: no such file or directory"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "capi register must be run from lapi" {
|
@test "capi register must be run from lapi" {
|
||||||
config_disable_lapi
|
config_disable_lapi
|
||||||
rune -1 cscli capi register --schmilblick githubciXXXXXXXXXXXXXXXXXXXXXXXX
|
rune -1 cscli capi register --schmilblick githubciXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
|
|
@ -46,9 +46,21 @@ teardown() {
|
||||||
assert_stderr --partial "loading console context from $CONTEXT_YAML"
|
assert_stderr --partial "loading console context from $CONTEXT_YAML"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "no error if context file is missing but not explicitly set" {
|
||||||
|
config_set "del(.crowdsec_service.console_context_path)"
|
||||||
|
rune -0 rm -f "$CONTEXT_YAML"
|
||||||
|
rune -0 cscli lapi context status --error
|
||||||
|
refute_stderr
|
||||||
|
assert_output --partial "No context found on this agent."
|
||||||
|
rune -0 "$CROWDSEC" -t
|
||||||
|
refute_stderr --partial "no such file or directory"
|
||||||
|
}
|
||||||
|
|
||||||
@test "error if context file is explicitly set but does not exist" {
|
@test "error if context file is explicitly set but does not exist" {
|
||||||
config_set ".crowdsec_service.console_context_path=strenv(CONTEXT_YAML)"
|
config_set ".crowdsec_service.console_context_path=strenv(CONTEXT_YAML)"
|
||||||
rune -0 rm -f "$CONTEXT_YAML"
|
rune -0 rm -f "$CONTEXT_YAML"
|
||||||
|
rune -1 cscli lapi context status --error
|
||||||
|
assert_stderr --partial "context.yaml: no such file or directory"
|
||||||
rune -1 "$CROWDSEC" -t
|
rune -1 "$CROWDSEC" -t
|
||||||
assert_stderr --partial "while checking console_context_path: stat $CONTEXT_YAML: no such file or directory"
|
assert_stderr --partial "while checking console_context_path: stat $CONTEXT_YAML: no such file or directory"
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ teardown() {
|
||||||
|
|
||||||
@test "capi_whitelists: file missing" {
|
@test "capi_whitelists: file missing" {
|
||||||
rune -0 wait-for \
|
rune -0 wait-for \
|
||||||
--err "capi whitelist file '$CAPI_WHITELISTS_YAML' does not exist" \
|
--err "while opening capi whitelist file: open $CAPI_WHITELISTS_YAML: no such file or directory" \
|
||||||
"${CROWDSEC}"
|
"${CROWDSEC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,16 @@ teardown() {
|
||||||
assert_output --partial 'crowdsecurity/iptables'
|
assert_output --partial 'crowdsecurity/iptables'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "cscli hub list (invalid index)" {
|
||||||
|
new_hub=$(jq <"$INDEX_PATH" '."appsec-rules"."crowdsecurity/vpatch-laravel-debug-mode".version="999"')
|
||||||
|
echo "$new_hub" >"$INDEX_PATH"
|
||||||
|
rune -0 cscli hub list --error
|
||||||
|
assert_stderr --partial "invalid hub item appsec-rules:crowdsecurity/vpatch-laravel-debug-mode: latest version missing from index"
|
||||||
|
|
||||||
|
rune -1 cscli appsec-rules install crowdsecurity/vpatch-laravel-debug-mode --force
|
||||||
|
assert_stderr --partial "error while installing 'crowdsecurity/vpatch-laravel-debug-mode': while downloading crowdsecurity/vpatch-laravel-debug-mode: latest hash missing from index"
|
||||||
|
}
|
||||||
|
|
||||||
@test "missing reference in hub index" {
|
@test "missing reference in hub index" {
|
||||||
new_hub=$(jq <"$INDEX_PATH" 'del(.parsers."crowdsecurity/smb-logs") | del (.scenarios."crowdsecurity/mysql-bf")')
|
new_hub=$(jq <"$INDEX_PATH" 'del(.parsers."crowdsecurity/smb-logs") | del (.scenarios."crowdsecurity/mysql-bf")')
|
||||||
echo "$new_hub" >"$INDEX_PATH"
|
echo "$new_hub" >"$INDEX_PATH"
|
||||||
|
|
90
wizard.sh
90
wizard.sh
|
@ -102,7 +102,7 @@ log_info() {
|
||||||
log_fatal() {
|
log_fatal() {
|
||||||
msg=$1
|
msg=$1
|
||||||
date=$(date "+%Y-%m-%d %H:%M:%S")
|
date=$(date "+%Y-%m-%d %H:%M:%S")
|
||||||
echo -e "${RED}FATA${NC}[${date}] crowdsec_wizard: ${msg}" 1>&2
|
echo -e "${RED}FATA${NC}[${date}] crowdsec_wizard: ${msg}" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,16 +129,16 @@ log_dbg() {
|
||||||
detect_services () {
|
detect_services () {
|
||||||
DETECTED_SERVICES=()
|
DETECTED_SERVICES=()
|
||||||
HMENU=()
|
HMENU=()
|
||||||
#list systemd services
|
# list systemd services
|
||||||
SYSTEMD_SERVICES=`systemctl --state=enabled list-unit-files '*.service' | cut -d ' ' -f1`
|
SYSTEMD_SERVICES=`systemctl --state=enabled list-unit-files '*.service' | cut -d ' ' -f1`
|
||||||
#raw ps
|
# raw ps
|
||||||
PSAX=`ps ax -o comm=`
|
PSAX=`ps ax -o comm=`
|
||||||
for SVC in ${SUPPORTED_SERVICES} ; do
|
for SVC in ${SUPPORTED_SERVICES} ; do
|
||||||
log_dbg "Checking if service '${SVC}' is running (ps+systemd)"
|
log_dbg "Checking if service '${SVC}' is running (ps+systemd)"
|
||||||
for SRC in "${SYSTEMD_SERVICES}" "${PSAX}" ; do
|
for SRC in "${SYSTEMD_SERVICES}" "${PSAX}" ; do
|
||||||
echo ${SRC} | grep ${SVC} >/dev/null
|
echo ${SRC} | grep ${SVC} >/dev/null
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
#on centos, apache2 is named httpd
|
# on centos, apache2 is named httpd
|
||||||
if [[ ${SVC} == "httpd" ]] ; then
|
if [[ ${SVC} == "httpd" ]] ; then
|
||||||
SVC="apache2";
|
SVC="apache2";
|
||||||
fi
|
fi
|
||||||
|
@ -152,12 +152,12 @@ detect_services () {
|
||||||
if [[ ${OSTYPE} == "linux-gnu" ]] || [[ ${OSTYPE} == "linux-gnueabihf" ]]; then
|
if [[ ${OSTYPE} == "linux-gnu" ]] || [[ ${OSTYPE} == "linux-gnueabihf" ]]; then
|
||||||
DETECTED_SERVICES+=("linux")
|
DETECTED_SERVICES+=("linux")
|
||||||
HMENU+=("linux" "on")
|
HMENU+=("linux" "on")
|
||||||
else
|
else
|
||||||
log_info "NOT A LINUX"
|
log_info "NOT A LINUX"
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
if [[ ${SILENT} == "false" ]]; then
|
if [[ ${SILENT} == "false" ]]; then
|
||||||
#we put whiptail results in an array, notice the dark magic fd redirection
|
# we put whiptail results in an array, notice the dark magic fd redirection
|
||||||
DETECTED_SERVICES=($(whiptail --separate-output --noitem --ok-button Continue --title "Services to monitor" --checklist "Detected services, uncheck to ignore. Ignored services won't be monitored." 18 70 10 ${HMENU[@]} 3>&1 1>&2 2>&3))
|
DETECTED_SERVICES=($(whiptail --separate-output --noitem --ok-button Continue --title "Services to monitor" --checklist "Detected services, uncheck to ignore. Ignored services won't be monitored." 18 70 10 ${HMENU[@]} 3>&1 1>&2 2>&3))
|
||||||
if [ $? -eq 1 ]; then
|
if [ $? -eq 1 ]; then
|
||||||
log_err "user bailed out at services selection"
|
log_err "user bailed out at services selection"
|
||||||
|
@ -189,28 +189,28 @@ log_locations[mysql]='/var/log/mysql/error.log'
|
||||||
log_locations[smb]='/var/log/samba*.log'
|
log_locations[smb]='/var/log/samba*.log'
|
||||||
log_locations[linux]='/var/log/syslog,/var/log/kern.log,/var/log/messages'
|
log_locations[linux]='/var/log/syslog,/var/log/kern.log,/var/log/messages'
|
||||||
|
|
||||||
#$1 is service name, such those in SUPPORTED_SERVICES
|
# $1 is service name, such those in SUPPORTED_SERVICES
|
||||||
find_logs_for() {
|
find_logs_for() {
|
||||||
ret=""
|
ret=""
|
||||||
x=${1}
|
x=${1}
|
||||||
#we have trailing and starting quotes because of whiptail
|
# we have trailing and starting quotes because of whiptail
|
||||||
SVC="${x%\"}"
|
SVC="${x%\"}"
|
||||||
SVC="${SVC#\"}"
|
SVC="${SVC#\"}"
|
||||||
DETECTED_LOGFILES=()
|
DETECTED_LOGFILES=()
|
||||||
HMENU=()
|
HMENU=()
|
||||||
#log_info "Searching logs for ${SVC} : ${log_locations[${SVC}]}"
|
# log_info "Searching logs for ${SVC} : ${log_locations[${SVC}]}"
|
||||||
|
|
||||||
#split the line into an array with ',' separator
|
# split the line into an array with ',' separator
|
||||||
OIFS=${IFS}
|
OIFS=${IFS}
|
||||||
IFS=',' read -r -a a <<< "${log_locations[${SVC}]},"
|
IFS=',' read -r -a a <<< "${log_locations[${SVC}]},"
|
||||||
IFS=${OIFS}
|
IFS=${OIFS}
|
||||||
#readarray -td, a <<<"${log_locations[${SVC}]},"; unset 'a[-1]';
|
# readarray -td, a <<<"${log_locations[${SVC}]},"; unset 'a[-1]';
|
||||||
for poss_path in "${a[@]}"; do
|
for poss_path in "${a[@]}"; do
|
||||||
#Split /var/log/nginx/*.log into '/var/log/nginx' and '*.log' so we can use find
|
# Split /var/log/nginx/*.log into '/var/log/nginx' and '*.log' so we can use find
|
||||||
path=${poss_path%/*}
|
path=${poss_path%/*}
|
||||||
fname=${poss_path##*/}
|
fname=${poss_path##*/}
|
||||||
candidates=`find "${path}" -type f -mtime -5 -ctime -5 -name "$fname"`
|
candidates=`find "${path}" -type f -mtime -5 -ctime -5 -name "$fname"`
|
||||||
#We have some candidates, add them
|
# We have some candidates, add them
|
||||||
for final_file in ${candidates} ; do
|
for final_file in ${candidates} ; do
|
||||||
log_dbg "Found logs file for '${SVC}': ${final_file}"
|
log_dbg "Found logs file for '${SVC}': ${final_file}"
|
||||||
DETECTED_LOGFILES+=(${final_file})
|
DETECTED_LOGFILES+=(${final_file})
|
||||||
|
@ -249,12 +249,12 @@ install_collection() {
|
||||||
in_array $collection "${DETECTED_SERVICES[@]}"
|
in_array $collection "${DETECTED_SERVICES[@]}"
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
HMENU+=("${collection}" "${description}" "ON")
|
HMENU+=("${collection}" "${description}" "ON")
|
||||||
#in case we're not in interactive mode, assume defaults
|
# in case we're not in interactive mode, assume defaults
|
||||||
COLLECTION_TO_INSTALL+=(${collection})
|
COLLECTION_TO_INSTALL+=(${collection})
|
||||||
else
|
else
|
||||||
if [[ ${collection} == "linux" ]]; then
|
if [[ ${collection} == "linux" ]]; then
|
||||||
HMENU+=("${collection}" "${description}" "ON")
|
HMENU+=("${collection}" "${description}" "ON")
|
||||||
#in case we're not in interactive mode, assume defaults
|
# in case we're not in interactive mode, assume defaults
|
||||||
COLLECTION_TO_INSTALL+=(${collection})
|
COLLECTION_TO_INSTALL+=(${collection})
|
||||||
else
|
else
|
||||||
HMENU+=("${collection}" "${description}" "OFF")
|
HMENU+=("${collection}" "${description}" "OFF")
|
||||||
|
@ -272,10 +272,10 @@ install_collection() {
|
||||||
|
|
||||||
for collection in "${COLLECTION_TO_INSTALL[@]}"; do
|
for collection in "${COLLECTION_TO_INSTALL[@]}"; do
|
||||||
log_info "Installing collection '${collection}'"
|
log_info "Installing collection '${collection}'"
|
||||||
${CSCLI_BIN_INSTALLED} collections install "${collection}" > /dev/null 2>&1 || log_err "fail to install collection ${collection}"
|
${CSCLI_BIN_INSTALLED} collections install "${collection}" --error
|
||||||
done
|
done
|
||||||
|
|
||||||
${CSCLI_BIN_INSTALLED} parsers install "crowdsecurity/whitelists" > /dev/null 2>&1 || log_err "fail to install collection crowdsec/whitelists"
|
${CSCLI_BIN_INSTALLED} parsers install "crowdsecurity/whitelists" --error
|
||||||
if [[ ${SILENT} == "false" ]]; then
|
if [[ ${SILENT} == "false" ]]; then
|
||||||
whiptail --msgbox "Out of safety, I installed a parser called 'crowdsecurity/whitelists'. This one will prevent private IP addresses from being banned, feel free to remove it any time." 20 50
|
whiptail --msgbox "Out of safety, I installed a parser called 'crowdsecurity/whitelists'. This one will prevent private IP addresses from being banned, feel free to remove it any time." 20 50
|
||||||
fi
|
fi
|
||||||
|
@ -285,14 +285,14 @@ install_collection() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
#$1 is the service name, $... is the list of candidate logs (from find_logs_for)
|
# $1 is the service name, $... is the list of candidate logs (from find_logs_for)
|
||||||
genyamllog() {
|
genyamllog() {
|
||||||
local service="${1}"
|
local service="${1}"
|
||||||
shift
|
shift
|
||||||
local files=("${@}")
|
local files=("${@}")
|
||||||
|
|
||||||
echo "#Generated acquisition file - wizard.sh (service: ${service}) / files : ${files[@]}" >> ${TMP_ACQUIS_FILE}
|
echo "#Generated acquisition file - wizard.sh (service: ${service}) / files : ${files[@]}" >> ${TMP_ACQUIS_FILE}
|
||||||
|
|
||||||
echo "filenames:" >> ${TMP_ACQUIS_FILE}
|
echo "filenames:" >> ${TMP_ACQUIS_FILE}
|
||||||
for fd in ${files[@]}; do
|
for fd in ${files[@]}; do
|
||||||
echo " - ${fd}" >> ${TMP_ACQUIS_FILE}
|
echo " - ${fd}" >> ${TMP_ACQUIS_FILE}
|
||||||
|
@ -306,9 +306,9 @@ genyamllog() {
|
||||||
genyamljournal() {
|
genyamljournal() {
|
||||||
local service="${1}"
|
local service="${1}"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
echo "#Generated acquisition file - wizard.sh (service: ${service}) / files : ${files[@]}" >> ${TMP_ACQUIS_FILE}
|
echo "#Generated acquisition file - wizard.sh (service: ${service}) / files : ${files[@]}" >> ${TMP_ACQUIS_FILE}
|
||||||
|
|
||||||
echo "journalctl_filter:" >> ${TMP_ACQUIS_FILE}
|
echo "journalctl_filter:" >> ${TMP_ACQUIS_FILE}
|
||||||
echo " - _SYSTEMD_UNIT="${service}".service" >> ${TMP_ACQUIS_FILE}
|
echo " - _SYSTEMD_UNIT="${service}".service" >> ${TMP_ACQUIS_FILE}
|
||||||
echo "labels:" >> ${TMP_ACQUIS_FILE}
|
echo "labels:" >> ${TMP_ACQUIS_FILE}
|
||||||
|
@ -318,7 +318,7 @@ genyamljournal() {
|
||||||
}
|
}
|
||||||
|
|
||||||
genacquisition() {
|
genacquisition() {
|
||||||
if skip_tmp_acquis; then
|
if skip_tmp_acquis; then
|
||||||
TMP_ACQUIS_FILE="${ACQUIS_TARGET}"
|
TMP_ACQUIS_FILE="${ACQUIS_TARGET}"
|
||||||
ACQUIS_FILE_MSG="acquisition file generated to: ${TMP_ACQUIS_FILE}"
|
ACQUIS_FILE_MSG="acquisition file generated to: ${TMP_ACQUIS_FILE}"
|
||||||
else
|
else
|
||||||
|
@ -336,7 +336,7 @@ genacquisition() {
|
||||||
log_info "using journald for '${PSVG}'"
|
log_info "using journald for '${PSVG}'"
|
||||||
genyamljournal ${PSVG}
|
genyamljournal ${PSVG}
|
||||||
fi;
|
fi;
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_cs_install () {
|
detect_cs_install () {
|
||||||
|
@ -371,7 +371,7 @@ check_cs_version () {
|
||||||
fi
|
fi
|
||||||
elif [[ $NEW_MINOR_VERSION -gt $CURRENT_MINOR_VERSION ]] ; then
|
elif [[ $NEW_MINOR_VERSION -gt $CURRENT_MINOR_VERSION ]] ; then
|
||||||
log_warn "new version ($NEW_CS_VERSION) is a minor upgrade !"
|
log_warn "new version ($NEW_CS_VERSION) is a minor upgrade !"
|
||||||
if [[ $ACTION != "upgrade" ]] ; then
|
if [[ $ACTION != "upgrade" ]] ; then
|
||||||
if [[ ${FORCE_MODE} == "false" ]]; then
|
if [[ ${FORCE_MODE} == "false" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "We recommend to upgrade with : sudo ./wizard.sh --upgrade "
|
echo "We recommend to upgrade with : sudo ./wizard.sh --upgrade "
|
||||||
|
@ -383,7 +383,7 @@ check_cs_version () {
|
||||||
fi
|
fi
|
||||||
elif [[ $NEW_PATCH_VERSION -gt $CURRENT_PATCH_VERSION ]] ; then
|
elif [[ $NEW_PATCH_VERSION -gt $CURRENT_PATCH_VERSION ]] ; then
|
||||||
log_warn "new version ($NEW_CS_VERSION) is a patch !"
|
log_warn "new version ($NEW_CS_VERSION) is a patch !"
|
||||||
if [[ $ACTION != "binupgrade" ]] ; then
|
if [[ $ACTION != "binupgrade" ]] ; then
|
||||||
if [[ ${FORCE_MODE} == "false" ]]; then
|
if [[ ${FORCE_MODE} == "false" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "We recommend to upgrade binaries only : sudo ./wizard.sh --binupgrade "
|
echo "We recommend to upgrade binaries only : sudo ./wizard.sh --binupgrade "
|
||||||
|
@ -406,7 +406,7 @@ check_cs_version () {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
#install crowdsec and cscli
|
# install crowdsec and cscli
|
||||||
install_crowdsec() {
|
install_crowdsec() {
|
||||||
mkdir -p "${CROWDSEC_DATA_DIR}"
|
mkdir -p "${CROWDSEC_DATA_DIR}"
|
||||||
(cd config && find patterns -type f -exec install -Dm 644 "{}" "${CROWDSEC_CONFIG_PATH}/{}" \; && cd ../) || exit
|
(cd config && find patterns -type f -exec install -Dm 644 "{}" "${CROWDSEC_CONFIG_PATH}/{}" \; && cd ../) || exit
|
||||||
|
@ -418,7 +418,7 @@ install_crowdsec() {
|
||||||
mkdir -p "${CROWDSEC_CONFIG_PATH}/appsec-rules" || exit
|
mkdir -p "${CROWDSEC_CONFIG_PATH}/appsec-rules" || exit
|
||||||
mkdir -p "${CROWDSEC_CONSOLE_DIR}" || exit
|
mkdir -p "${CROWDSEC_CONSOLE_DIR}" || exit
|
||||||
|
|
||||||
#tmp
|
# tmp
|
||||||
mkdir -p /tmp/data
|
mkdir -p /tmp/data
|
||||||
mkdir -p /etc/crowdsec/hub/
|
mkdir -p /etc/crowdsec/hub/
|
||||||
install -v -m 600 -D "./config/${CLIENT_SECRETS}" "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
|
install -v -m 600 -D "./config/${CLIENT_SECRETS}" "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
|
||||||
|
@ -490,7 +490,7 @@ install_bins() {
|
||||||
install -v -m 755 -D "${CSCLI_BIN}" "${CSCLI_BIN_INSTALLED}" 1> /dev/null || exit
|
install -v -m 755 -D "${CSCLI_BIN}" "${CSCLI_BIN_INSTALLED}" 1> /dev/null || exit
|
||||||
which systemctl && systemctl is-active --quiet crowdsec
|
which systemctl && systemctl is-active --quiet crowdsec
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
systemctl stop crowdsec
|
systemctl stop crowdsec
|
||||||
fi
|
fi
|
||||||
install_plugins
|
install_plugins
|
||||||
symlink_bins
|
symlink_bins
|
||||||
|
@ -508,7 +508,7 @@ symlink_bins() {
|
||||||
delete_bins() {
|
delete_bins() {
|
||||||
log_info "Removing crowdsec binaries"
|
log_info "Removing crowdsec binaries"
|
||||||
rm -f ${CROWDSEC_BIN_INSTALLED}
|
rm -f ${CROWDSEC_BIN_INSTALLED}
|
||||||
rm -f ${CSCLI_BIN_INSTALLED}
|
rm -f ${CSCLI_BIN_INSTALLED}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_plugins() {
|
delete_plugins() {
|
||||||
|
@ -535,7 +535,7 @@ install_plugins(){
|
||||||
}
|
}
|
||||||
|
|
||||||
check_running_bouncers() {
|
check_running_bouncers() {
|
||||||
#when uninstalling, check if user still has bouncers
|
# when uninstalling, check if user still has bouncers
|
||||||
BOUNCERS_COUNT=$(${CSCLI_BIN} bouncers list -o=raw | tail -n +2 | wc -l)
|
BOUNCERS_COUNT=$(${CSCLI_BIN} bouncers list -o=raw | tail -n +2 | wc -l)
|
||||||
if [[ ${BOUNCERS_COUNT} -gt 0 ]] ; then
|
if [[ ${BOUNCERS_COUNT} -gt 0 ]] ; then
|
||||||
if [[ ${FORCE_MODE} == "false" ]]; then
|
if [[ ${FORCE_MODE} == "false" ]]; then
|
||||||
|
@ -646,7 +646,7 @@ main() {
|
||||||
then
|
then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$1" == "uninstall" ]];
|
if [[ "$1" == "uninstall" ]];
|
||||||
then
|
then
|
||||||
if ! [ $(id -u) = 0 ]; then
|
if ! [ $(id -u) = 0 ]; then
|
||||||
|
@ -685,11 +685,11 @@ main() {
|
||||||
log_info "installing crowdsec"
|
log_info "installing crowdsec"
|
||||||
install_crowdsec
|
install_crowdsec
|
||||||
log_dbg "configuring ${CSCLI_BIN_INSTALLED}"
|
log_dbg "configuring ${CSCLI_BIN_INSTALLED}"
|
||||||
${CSCLI_BIN_INSTALLED} hub update > /dev/null 2>&1 || (log_err "fail to update crowdsec hub. exiting" && exit 1)
|
${CSCLI_BIN_INSTALLED} hub update --error || (log_err "fail to update crowdsec hub. exiting" && exit 1)
|
||||||
|
|
||||||
# detect running services
|
# detect running services
|
||||||
detect_services
|
detect_services
|
||||||
if ! [ ${#DETECTED_SERVICES[@]} -gt 0 ] ; then
|
if ! [ ${#DETECTED_SERVICES[@]} -gt 0 ] ; then
|
||||||
log_err "No detected or selected services, stopping."
|
log_err "No detected or selected services, stopping."
|
||||||
exit 1
|
exit 1
|
||||||
fi;
|
fi;
|
||||||
|
@ -711,11 +711,11 @@ main() {
|
||||||
|
|
||||||
# api register
|
# api register
|
||||||
${CSCLI_BIN_INSTALLED} machines add --force "$(cat /etc/machine-id)" -a -f "${CROWDSEC_CONFIG_PATH}/${CLIENT_SECRETS}" || log_fatal "unable to add machine to the local API"
|
${CSCLI_BIN_INSTALLED} machines add --force "$(cat /etc/machine-id)" -a -f "${CROWDSEC_CONFIG_PATH}/${CLIENT_SECRETS}" || log_fatal "unable to add machine to the local API"
|
||||||
log_dbg "Crowdsec LAPI registered"
|
log_dbg "Crowdsec LAPI registered"
|
||||||
|
|
||||||
${CSCLI_BIN_INSTALLED} capi register || log_fatal "unable to register to the Central API"
|
${CSCLI_BIN_INSTALLED} capi register || log_fatal "unable to register to the Central API"
|
||||||
log_dbg "Crowdsec CAPI registered"
|
log_dbg "Crowdsec CAPI registered"
|
||||||
|
|
||||||
systemctl enable -q crowdsec >/dev/null || log_fatal "unable to enable crowdsec"
|
systemctl enable -q crowdsec >/dev/null || log_fatal "unable to enable crowdsec"
|
||||||
systemctl start crowdsec >/dev/null || log_fatal "unable to start crowdsec"
|
systemctl start crowdsec >/dev/null || log_fatal "unable to start crowdsec"
|
||||||
log_info "enabling and starting crowdsec daemon"
|
log_info "enabling and starting crowdsec daemon"
|
||||||
|
@ -729,7 +729,7 @@ main() {
|
||||||
rm -f "${TMP_ACQUIS_FILE}"
|
rm -f "${TMP_ACQUIS_FILE}"
|
||||||
fi
|
fi
|
||||||
detect_services
|
detect_services
|
||||||
if [[ ${DETECTED_SERVICES} == "" ]] ; then
|
if [[ ${DETECTED_SERVICES} == "" ]] ; then
|
||||||
log_err "No detected or selected services, stopping."
|
log_err "No detected or selected services, stopping."
|
||||||
exit
|
exit
|
||||||
fi;
|
fi;
|
||||||
|
@ -757,7 +757,7 @@ usage() {
|
||||||
echo " ./wizard.sh --docker-mode Will install crowdsec without systemd and generate random machine-id"
|
echo " ./wizard.sh --docker-mode Will install crowdsec without systemd and generate random machine-id"
|
||||||
echo " ./wizard.sh -n|--noop Do nothing"
|
echo " ./wizard.sh -n|--noop Do nothing"
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $# -eq 0 ]]; then
|
if [[ $# -eq 0 ]]; then
|
||||||
|
@ -770,15 +770,15 @@ do
|
||||||
case ${key} in
|
case ${key} in
|
||||||
--uninstall)
|
--uninstall)
|
||||||
ACTION="uninstall"
|
ACTION="uninstall"
|
||||||
shift #past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--binupgrade)
|
--binupgrade)
|
||||||
ACTION="binupgrade"
|
ACTION="binupgrade"
|
||||||
shift #past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--upgrade)
|
--upgrade)
|
||||||
ACTION="upgrade"
|
ACTION="upgrade"
|
||||||
shift #past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
-i|--install)
|
-i|--install)
|
||||||
ACTION="install"
|
ACTION="install"
|
||||||
|
@ -813,11 +813,11 @@ do
|
||||||
-f|--force)
|
-f|--force)
|
||||||
FORCE_MODE="true"
|
FORCE_MODE="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-v|--verbose)
|
-v|--verbose)
|
||||||
DEBUG_MODE="true"
|
DEBUG_MODE="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|
Loading…
Reference in a new issue