diff --git a/.github/workflows/ci_functests-install.yml b/.github/workflows/ci_functests-install.yml index cf7d1d7af..066ac595b 100644 --- a/.github/workflows/ci_functests-install.yml +++ b/.github/workflows/ci_functests-install.yml @@ -21,10 +21,10 @@ jobs: name: Install generated release and perform functional tests runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.16 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/ci_go-test.yml b/.github/workflows/ci_go-test.yml index 05930e51b..599d1899b 100644 --- a/.github/workflows/ci_go-test.yml +++ b/.github/workflows/ci_go-test.yml @@ -52,10 +52,10 @@ jobs: --health-timeout=5s --health-retries=3 steps: - - name: Set up Go 1.13 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.16 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/ci_hub-tests.yml b/.github/workflows/ci_hub-tests.yml index 7f34dec75..fca57f4b6 100644 --- a/.github/workflows/ci_hub-tests.yml +++ b/.github/workflows/ci_hub-tests.yml @@ -19,10 +19,10 @@ jobs: name: Hub Parser/Scenario tests runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.16 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/release_publish-package.yml b/.github/workflows/release_publish-package.yml index 0f9dc81d3..fa035a05e 100644 --- a/.github/workflows/release_publish-package.yml +++ b/.github/workflows/release_publish-package.yml @@ -10,10 +10,10 @@ jobs: name: Build and upload binary package runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.16 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -29,10 +29,10 @@ jobs: name: Build and upload binary package runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.16 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/LICENSE b/LICENSE index 635963690..92d86fc27 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2021 crowdsecurity +Copyright (c) 2020-2021 Crowdsec Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 7625bf96a..cedc02be0 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,15 @@ DATA_PREFIX = $(PREFIX)"/var/run/crowdsec/" PID_DIR = $(PREFIX)"/var/run/" CROWDSEC_FOLDER = "./cmd/crowdsec" CSCLI_FOLDER = "./cmd/crowdsec-cli/" +HTTP_PLUGIN_FOLDER = "./plugins/notifications/http" +SLACK_PLUGIN_FOLDER = "./plugins/notifications/slack" +SPLUNK_PLUGIN_FOLDER = "./plugins/notifications/splunk" +HTTP_PLUGIN_BIN = "notification-http" +SLACK_PLUGIN_BIN = "notification-slack" +SPLUNK_PLUGIN_BIN = "notification-splunk" +HTTP_PLUGIN_CONFIG = "http.yaml" +SLACK_PLUGIN_CONFIG = "slack.yaml" +SPLUNK_PLUGIN_CONFIG = "splunk.yaml" CROWDSEC_BIN = "crowdsec" CSCLI_BIN = "cscli" BUILD_CMD = "build" @@ -53,9 +62,13 @@ RELDIR = crowdsec-$(BUILD_VERSION) all: clean test build -build: goversion crowdsec cscli +build: goversion crowdsec cscli plugins -static: goversion crowdsec_static cscli_static +static: crowdsec_static cscli_static plugins_static + +plugins: http-plugin slack-plugin splunk-plugin + +plugins_static: http-plugin_static slack-plugin_static splunk-plugin_static goversion: @if [ $(GO_MAJOR_VERSION) -gt $(MINIMUM_SUPPORTED_GO_MAJOR_VERSION) ]; then \ @@ -76,74 +89,67 @@ clean: @rm -f *.log @rm -f crowdsec-release.tgz -cscli: -ifeq ($(lastword $(RESPECT_VERSION)), $(CURRENT_GOVERSION)) +cscli: goversion @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(CSCLI_FOLDER) build --no-print-directory -else - @echo "Required golang version is $(REQUIRE_GOVERSION). The current one is $(CURRENT_GOVERSION). Exiting.." - @exit 1; -endif - -crowdsec: -ifeq ($(lastword $(RESPECT_VERSION)), $(CURRENT_GOVERSION)) +crowdsec: goversion @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(CROWDSEC_FOLDER) build --no-print-directory -else - @echo "Required golang version is $(REQUIRE_GOVERSION). The current one is $(CURRENT_GOVERSION). Exiting.." - @exit 1; -endif + +http-plugin: goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(HTTP_PLUGIN_FOLDER) build --no-print-directory + +slack-plugin: goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(SLACK_PLUGIN_FOLDER) build --no-print-directory + +splunk-plugin: goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(SPLUNK_PLUGIN_FOLDER) build --no-print-directory -cscli_static: -ifeq ($(lastword $(RESPECT_VERSION)), $(CURRENT_GOVERSION)) +cscli_static: goversion @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(CSCLI_FOLDER) static --no-print-directory -else - @echo "Required golang version is $(REQUIRE_GOVERSION). The current one is $(CURRENT_GOVERSION). Exiting.." - @exit 1; -endif - -crowdsec_static: -ifeq ($(lastword $(RESPECT_VERSION)), $(CURRENT_GOVERSION)) +crowdsec_static: goversion @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(CROWDSEC_FOLDER) static --no-print-directory -else - @echo "Required golang version is $(REQUIRE_GOVERSION). The current one is $(CURRENT_GOVERSION). Exiting.." - @exit 1; -endif -#.PHONY: test -test: -ifeq ($(lastword $(RESPECT_VERSION)), $(CURRENT_GOVERSION)) +http-plugin_static: goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(HTTP_PLUGIN_FOLDER) static --no-print-directory + +slack-plugin_static: goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(SLACK_PLUGIN_FOLDER) static --no-print-directory + +splunk-plugin_static:goversion + @GOARCH=$(GOARCH) GOOS=$(GOOS) $(MAKE) -C $(SPLUNK_PLUGIN_FOLDER) static --no-print-directory + +test: goversion @$(MAKE) -C $(CROWDSEC_FOLDER) test --no-print-directory -else - @echo "Required golang version is $(REQUIRE_GOVERSION). The current one is $(CURRENT_GOVERSION). Exiting.." - @exit 1; -endif + +package: + @echo Building Release to dir $(RELDIR) + @mkdir -p $(RELDIR)/cmd/crowdsec + @mkdir -p $(RELDIR)/cmd/crowdsec-cli + @mkdir -p $(RELDIR)/$(subst ./,,$(HTTP_PLUGIN_FOLDER)) + @mkdir -p $(RELDIR)/$(subst ./,,$(SLACK_PLUGIN_FOLDER)) + @mkdir -p $(RELDIR)/$(subst ./,,$(SPLUNK_PLUGIN_FOLDER)) + + @cp $(CROWDSEC_FOLDER)/$(CROWDSEC_BIN) $(RELDIR)/cmd/crowdsec + @cp $(CSCLI_FOLDER)/$(CSCLI_BIN) $(RELDIR)/cmd/crowdsec-cli + @cp $(HTTP_PLUGIN_FOLDER)/$(HTTP_PLUGIN_BIN) $(RELDIR)/$(subst ./,,$(HTTP_PLUGIN_FOLDER)) + @cp $(SLACK_PLUGIN_FOLDER)/$(SLACK_PLUGIN_BIN) $(RELDIR)/$(subst ./,,$(SLACK_PLUGIN_FOLDER)) + @cp $(SPLUNK_PLUGIN_FOLDER)/$(SPLUNK_PLUGIN_BIN) $(RELDIR)/$(subst ./,,$(SPLUNK_PLUGIN_FOLDER)) + @cp $(HTTP_PLUGIN_FOLDER)/$(HTTP_PLUGIN_CONFIG) $(RELDIR)/$(subst ./,,$(HTTP_PLUGIN_FOLDER)) + @cp $(SLACK_PLUGIN_FOLDER)/$(SLACK_PLUGIN_CONFIG) $(RELDIR)/$(subst ./,,$(SLACK_PLUGIN_FOLDER)) + @cp $(SPLUNK_PLUGIN_FOLDER)/$(SPLUNK_PLUGIN_CONFIG) $(RELDIR)/$(subst ./,,$(SPLUNK_PLUGIN_FOLDER)) + @cp -R ./config/ $(RELDIR) + @cp wizard.sh $(RELDIR) + @cp scripts/test_env.sh $(RELDIR) + @tar cvzf crowdsec-release.tgz $(RELDIR) .PHONY: check_release check_release: @if [ -d $(RELDIR) ]; then echo "$(RELDIR) already exists, abort" ; exit 1 ; fi .PHONY: -release: check_release build - @echo Building Release to dir $(RELDIR) - @mkdir -p $(RELDIR)/cmd/crowdsec - @mkdir -p $(RELDIR)/cmd/crowdsec-cli - @cp $(CROWDSEC_FOLDER)/$(CROWDSEC_BIN) $(RELDIR)/cmd/crowdsec - @cp $(CSCLI_FOLDER)/$(CSCLI_BIN) $(RELDIR)/cmd/crowdsec-cli - @cp -R ./config/ $(RELDIR) - @cp wizard.sh $(RELDIR) - @cp scripts/test_env.sh $(RELDIR) - @tar cvzf crowdsec-release.tgz $(RELDIR) +release: check_release build package .PHONY: -release_static: check_release static - @echo Building Release to dir $(RELDIR) - @mkdir -p $(RELDIR)/cmd/crowdsec - @mkdir -p $(RELDIR)/cmd/crowdsec-cli - @cp $(CROWDSEC_FOLDER)/$(CROWDSEC_BIN) $(RELDIR)/cmd/crowdsec - @cp $(CSCLI_FOLDER)/$(CSCLI_BIN) $(RELDIR)/cmd/crowdsec-cli - @cp -R ./config/ $(RELDIR) - @cp wizard.sh $(RELDIR) - @cp scripts/test_env.sh $(RELDIR) - @tar cvzf crowdsec-release-static.tgz $(RELDIR) +release_static: check_release static package \ No newline at end of file diff --git a/cmd/crowdsec/api.go b/cmd/crowdsec/api.go index 812646112..b3b50439d 100644 --- a/cmd/crowdsec/api.go +++ b/cmd/crowdsec/api.go @@ -6,6 +6,7 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/apiserver" "github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/types" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -15,6 +16,21 @@ func initAPIServer(cConfig *csconfig.Config) (*apiserver.APIServer, error) { return nil, fmt.Errorf("unable to run local API: %s", err) } + if hasPlugins(cConfig.API.Server.Profiles) { + log.Info("initiating plugin broker") + err = pluginBroker.Init(cConfig.API.Server.Profiles, cConfig.ConfigPaths) + if err != nil { + return nil, fmt.Errorf("unable to run local API: %s", err) + } + log.Info("initiated plugin broker") + apiServer.AttachPluginBroker(&pluginBroker) + } + + err = apiServer.InitController() + if err != nil { + return nil, errors.Wrap(err, "unable to run local API") + } + return apiServer, nil } @@ -27,7 +43,14 @@ func serveAPIServer(apiServer *apiserver.APIServer) { log.Fatalf(err.Error()) } }() + + pluginTomb.Go(func() error { + pluginBroker.Run(&pluginTomb) + return nil + }) + <-apiTomb.Dying() // lock until go routine is dying + pluginTomb.Kill(nil) log.Infof("serve: shutting down api server") if err := apiServer.Shutdown(); err != nil { return err @@ -35,3 +58,12 @@ func serveAPIServer(apiServer *apiserver.APIServer) { return nil }) } + +func hasPlugins(profiles []*csconfig.ProfileCfg) bool { + for _, profile := range profiles { + if len(profile.Notifications) != 0 { + return true + } + } + return false +} diff --git a/cmd/crowdsec/main.go b/cmd/crowdsec/main.go index 15431f180..ca91160f7 100644 --- a/cmd/crowdsec/main.go +++ b/cmd/crowdsec/main.go @@ -4,14 +4,14 @@ import ( "flag" "fmt" "os" + "sort" _ "net/http/pprof" "time" - "sort" - "github.com/crowdsecurity/crowdsec/pkg/acquisition" "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/cwhub" "github.com/crowdsecurity/crowdsec/pkg/cwversion" leaky "github.com/crowdsecurity/crowdsec/pkg/leakybucket" @@ -33,6 +33,7 @@ var ( outputsTomb tomb.Tomb apiTomb tomb.Tomb crowdsecTomb tomb.Tomb + pluginTomb tomb.Tomb flags *Flags @@ -44,6 +45,7 @@ var ( outputEventChan chan types.Event //the buckets init returns its own chan that is used for multiplexing /*settings*/ lastProcessedItem time.Time /*keep track of last item timestamp in time-machine. it is used to GC buckets when we dump them.*/ + pluginBroker csplugin.PluginBroker ) type Flags struct { diff --git a/cmd/crowdsec/serve.go b/cmd/crowdsec/serve.go index 959e1fd64..5c4207dbc 100644 --- a/cmd/crowdsec/serve.go +++ b/cmd/crowdsec/serve.go @@ -49,6 +49,7 @@ func reloadHandler(sig os.Signal, cConfig *csconfig.Config) error { outputsTomb = tomb.Tomb{} apiTomb = tomb.Tomb{} crowdsecTomb = tomb.Tomb{} + pluginTomb = tomb.Tomb{} cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI) if err != nil { @@ -217,7 +218,7 @@ func Serve(cConfig *csconfig.Config) error { outputsTomb = tomb.Tomb{} apiTomb = tomb.Tomb{} crowdsecTomb = tomb.Tomb{} - + pluginTomb = tomb.Tomb{} if !cConfig.DisableAPI { apiServer, err := initAPIServer(cConfig) if err != nil { @@ -240,6 +241,7 @@ func Serve(cConfig *csconfig.Config) error { } if flags.TestMode { log.Infof("test done") + pluginBroker.Kill() os.Exit(0) } diff --git a/config/config.yaml b/config/config.yaml index 3f9777a80..57b3dd8e6 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -11,6 +11,8 @@ config_paths: simulation_path: /etc/crowdsec/simulation.yaml hub_dir: /etc/crowdsec/hub/ index_path: /etc/crowdsec/hub/.index.json + notification_dir: /etc/crowdsec/notifications/ + plugin_dir: /var/lib/crowdsec/plugins/ crowdsec_service: acquisition_path: /etc/crowdsec/acquis.yaml parser_routines: 1 diff --git a/config/profiles.yaml b/config/profiles.yaml index 0fc6d4069..b24eabb4f 100644 --- a/config/profiles.yaml +++ b/config/profiles.yaml @@ -5,4 +5,8 @@ filters: decisions: - type: ban duration: 4h +# notifications: +# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this. +# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this. +# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this. on_success: break diff --git a/debian/rules b/debian/rules index 09bb60b9c..090565799 100755 --- a/debian/rules +++ b/debian/rules @@ -28,6 +28,19 @@ override_dh_auto_install: mkdir -p debian/crowdsec/usr/share/crowdsec mkdir -p debian/crowdsec/etc/crowdsec/hub/ mkdir -p debian/crowdsec/usr/share/crowdsec/config + + + mkdir -p debian/crowdsec/var/lib/crowdsec/plugins/ + mkdir -p debian/crowdsec/etc/crowdsec/notifications/ + + install -m 551 plugins/notifications/slack/notification-slack debian/crowdsec/var/lib/crowdsec/plugins/ + install -m 551 plugins/notifications/http/notification-http debian/crowdsec/var/lib/crowdsec/plugins/ + install -m 551 plugins/notifications/splunk/notification-splunk debian/crowdsec/var/lib/crowdsec/plugins/ + + cp plugins/notifications/slack/slack.yaml debian/crowdsec/etc/crowdsec/notifications/ + cp plugins/notifications/http/http.yaml debian/crowdsec/etc/crowdsec/notifications/ + cp plugins/notifications/splunk/splunk.yaml debian/crowdsec/etc/crowdsec/notifications/ + cp cmd/crowdsec/crowdsec debian/crowdsec/usr/bin cp cmd/crowdsec-cli/cscli debian/crowdsec/usr/bin cp wizard.sh debian/crowdsec/usr/share/crowdsec diff --git a/go.mod b/go.mod index 52e14db4a..103b7f007 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,9 @@ go 1.13 require ( entgo.io/ent v0.7.0 github.com/AlecAivazis/survey/v2 v2.2.7 + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible github.com/Microsoft/go-winio v0.4.16 // indirect github.com/alexliesenfeld/health v0.5.1 github.com/antonmedv/expr v1.8.9 @@ -32,7 +35,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.0.0 github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-plugin v1.4.2 github.com/hashicorp/go-version v1.2.1 + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/influxdata/go-syslog/v3 v3.0.0 github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.0 @@ -41,6 +48,7 @@ require ( github.com/mattn/go-runewidth v0.0.10 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/morikuni/aec v1.0.0 // indirect @@ -68,7 +76,8 @@ require ( golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/text v0.3.5 // indirect google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f // indirect - google.golang.org/grpc v1.35.0 // indirect + google.golang.org/grpc v1.35.0 + google.golang.org/protobuf v1.25.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index d249fd2be..c925b781b 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,12 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= @@ -144,6 +150,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= @@ -308,6 +315,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -364,9 +372,13 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -383,16 +395,24 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/go-syslog/v3 v3.0.0 h1:jichmjSZlYK0VMmlz+k4WeOQd7z745YLsvGMqwtYt4I= github.com/influxdata/go-syslog/v3 v3.0.0/go.mod h1:tulsOp+CecTAYC27u9miMgq21GqXRW6VdKbOG+QSP4Q= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -459,11 +479,13 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -482,8 +504,12 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -494,6 +520,8 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -521,6 +549,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.6 h1:11TGpSHY7Esh/i/qnq02Jo5oVrI1Gue8Slbq0ujPZFQ= github.com/nxadm/tail v1.4.6/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -743,6 +772,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -813,6 +843,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -898,6 +929,7 @@ google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -911,6 +943,7 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f h1:izedQ6yVIc5mZsRuXzmSreCOlzI0lCU1HpG8yEdMiKw= google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -923,6 +956,7 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= diff --git a/pkg/apiserver/alerts_test.go b/pkg/apiserver/alerts_test.go index 8522b128a..5b3f4fcaa 100644 --- a/pkg/apiserver/alerts_test.go +++ b/pkg/apiserver/alerts_test.go @@ -7,9 +7,11 @@ import ( "net/http" "net/http/httptest" "strings" + "sync" "testing" "time" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/models" "github.com/gin-gonic/gin" @@ -23,11 +25,18 @@ func InitMachineTest() (*gin.Engine, models.WatcherAuthResponse, error) { return nil, models.WatcherAuthResponse{}, fmt.Errorf("unable to run local API: %s", err) } - body, err := CreateTestMachine(router) + loginResp, err := LoginToTestAPI(router) if err != nil { return nil, models.WatcherAuthResponse{}, fmt.Errorf("%s", err.Error()) } + return router, loginResp, nil +} +func LoginToTestAPI(router *gin.Engine) (models.WatcherAuthResponse, error) { + body, err := CreateTestMachine(router) + if err != nil { + return models.WatcherAuthResponse{}, fmt.Errorf("%s", err.Error()) + } err = ValidateMachine("test") if err != nil { log.Fatalln(err.Error()) @@ -41,10 +50,14 @@ func InitMachineTest() (*gin.Engine, models.WatcherAuthResponse, error) { loginResp := models.WatcherAuthResponse{} err = json.NewDecoder(w.Body).Decode(&loginResp) if err != nil { - log.Fatalln(err.Error()) + return models.WatcherAuthResponse{}, fmt.Errorf("%s", err.Error()) } + return loginResp, nil +} - return router, loginResp, nil +func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthResponse) { + request.Header.Add("User-Agent", UserAgent) + request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authResponse.Token)) } func TestSimulatedAlert(t *testing.T) { @@ -61,15 +74,13 @@ func TestSimulatedAlert(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) //exclude decision in simulation mode w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?simulated=false", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) 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 `) @@ -77,8 +88,7 @@ func TestSimulatedAlert(t *testing.T) { //include decision in simulation mode w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?simulated=true", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) 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 `) @@ -94,8 +104,7 @@ func TestCreateAlert(t *testing.T) { // Create Alert with invalid format w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader("test")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) @@ -110,8 +119,7 @@ func TestCreateAlert(t *testing.T) { w = httptest.NewRecorder() req, _ = http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) @@ -126,14 +134,56 @@ func TestCreateAlert(t *testing.T) { w = httptest.NewRecorder() req, _ = http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 201, w.Code) assert.Equal(t, "[\"1\"]", w.Body.String()) } +func TestCreateAlertChannels(t *testing.T) { + + apiServer, err := NewAPIServer() + if err != nil { + log.Fatalln(err.Error()) + } + apiServer.controller.PluginChannel = make(chan csplugin.ProfileAlert) + apiServer.InitController() + + loginResp, err := LoginToTestAPI(apiServer.router) + if err != nil { + log.Fatalln(err.Error()) + } + + alertContentBytes, err := ioutil.ReadFile("./tests/alert_ssh-bf.json") + if err != nil { + log.Fatal(err) + } + alertContent := string(alertContentBytes) + + var pd csplugin.ProfileAlert + var wg sync.WaitGroup + + wg.Add(1) + go func() { + pd = <-apiServer.controller.PluginChannel + wg.Done() + }() + + go func() { + for { + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) + AddAuthHeaders(req, loginResp) + apiServer.controller.Router.ServeHTTP(w, req) + break + } + }() + wg.Wait() + assert.Equal(t, len(pd.Alert.Decisions), 1) + apiServer.Close() +} + func TestAlertListFilters(t *testing.T) { router, loginResp, err := InitMachineTest() if err != nil { @@ -163,15 +213,13 @@ func TestAlertListFilters(t *testing.T) { //create one alert w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) //bad filter w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?test=test", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String()) @@ -179,8 +227,7 @@ func TestAlertListFilters(t *testing.T) { //get without filters w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) //check alert and decision @@ -190,8 +237,7 @@ func TestAlertListFilters(t *testing.T) { //test decision_type filter (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?decision_type=ban", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -200,8 +246,7 @@ func TestAlertListFilters(t *testing.T) { //test decision_type filter (bad value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?decision_type=ratata", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -209,8 +254,7 @@ func TestAlertListFilters(t *testing.T) { //test scope (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?scope=Ip", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -219,8 +263,7 @@ func TestAlertListFilters(t *testing.T) { //test scope (bad value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?scope=rarara", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -228,8 +271,7 @@ func TestAlertListFilters(t *testing.T) { //test scenario (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -238,8 +280,7 @@ func TestAlertListFilters(t *testing.T) { //test scenario (bad value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?scenario=crowdsecurity/nope", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -247,8 +288,7 @@ func TestAlertListFilters(t *testing.T) { //test ip (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?ip=91.121.79.195", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -257,8 +297,7 @@ func TestAlertListFilters(t *testing.T) { //test ip (bad value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?ip=99.122.77.195", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -266,8 +305,7 @@ func TestAlertListFilters(t *testing.T) { //test ip (invalid value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?ip=gruueq", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) 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()) @@ -275,8 +313,7 @@ func TestAlertListFilters(t *testing.T) { //test range (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?range=91.121.79.0/24&contains=false", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -285,8 +322,7 @@ func TestAlertListFilters(t *testing.T) { //test range w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?range=99.122.77.0/24&contains=false", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -294,8 +330,7 @@ func TestAlertListFilters(t *testing.T) { //test range (invalid value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?range=ratata", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) 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()) @@ -303,8 +338,7 @@ func TestAlertListFilters(t *testing.T) { //test since (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?since=1h", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -313,8 +347,7 @@ func TestAlertListFilters(t *testing.T) { //test since (ok but yelds no results) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?since=1ns", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -322,8 +355,7 @@ func TestAlertListFilters(t *testing.T) { //test since (invalid value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?since=1zuzu", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`) @@ -331,8 +363,7 @@ func TestAlertListFilters(t *testing.T) { //test until (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?until=1ns", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -341,8 +372,7 @@ func TestAlertListFilters(t *testing.T) { //test until (ok but no return) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?until=1m", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -350,8 +380,7 @@ func TestAlertListFilters(t *testing.T) { //test until (invalid value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?until=1zuzu", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`) @@ -359,8 +388,7 @@ func TestAlertListFilters(t *testing.T) { //test simulated (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?simulated=true", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -369,8 +397,7 @@ func TestAlertListFilters(t *testing.T) { //test simulated (ok) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?simulated=false", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -379,8 +406,7 @@ func TestAlertListFilters(t *testing.T) { //test has active decision w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=true", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ") @@ -389,8 +415,7 @@ func TestAlertListFilters(t *testing.T) { //test has active decision w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=false", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, "null", w.Body.String()) @@ -398,8 +423,7 @@ func TestAlertListFilters(t *testing.T) { //test has active decision (invalid value) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=ratatqata", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) 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()) @@ -421,14 +445,12 @@ func TestAlertBulkInsert(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) } @@ -447,15 +469,13 @@ func TestListAlert(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // List Alert with invalid filter w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts?test=test", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String()) @@ -463,8 +483,7 @@ func TestListAlert(t *testing.T) { // List Alert w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/v1/alerts", nil) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) @@ -515,15 +534,13 @@ func TestDeleteAlert(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent)) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // Fail Delete Alert w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/alerts", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) req.RemoteAddr = "127.0.0.2:4242" router.ServeHTTP(w, req) @@ -533,8 +550,7 @@ func TestDeleteAlert(t *testing.T) { // Delete Alert w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/alerts", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) req.RemoteAddr = "127.0.0.1:4242" router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 0cf553b8b..1f5013984 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -12,6 +12,7 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers" "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/database" "github.com/crowdsecurity/crowdsec/pkg/types" "github.com/gin-gonic/gin" @@ -186,10 +187,6 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) { controller.CAPIChan = nil } - if err := controller.Init(); err != nil { - return &APIServer{}, err - } - return &APIServer{ URL: config.ListenURI, TLS: config.TLS, @@ -288,3 +285,12 @@ func (s *APIServer) Shutdown() error { } return nil } + +func (s *APIServer) AttachPluginBroker(broker *csplugin.PluginBroker) { + s.controller.PluginChannel = broker.PluginChannel +} + +func (s *APIServer) InitController() error { + err := s.controller.Init() + return err +} diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 2f0888ad9..8881b49ed 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -87,9 +87,8 @@ func LoadTestConfigForwardedFor() csconfig.Config { return config } -func NewAPITest() (*gin.Engine, error) { +func NewAPIServer() (*APIServer, error) { config := LoadTestConfig() - os.Remove("./ent") apiServer, err := NewServer(config.API.Server) if err != nil { @@ -97,6 +96,18 @@ func NewAPITest() (*gin.Engine, error) { } log.Printf("Creating new API server") gin.SetMode(gin.TestMode) + return apiServer, nil +} + +func NewAPITest() (*gin.Engine, error) { + apiServer, err := NewAPIServer() + if err != nil { + return nil, fmt.Errorf("unable to run local API: %s", err) + } + err = apiServer.InitController() + if err != nil { + return nil, fmt.Errorf("unable to run local API: %s", err) + } router, err := apiServer.Router() if err != nil { return nil, fmt.Errorf("unable to run local API: %s", err) @@ -112,6 +123,10 @@ func NewAPITestForwardedFor() (*gin.Engine, error) { if err != nil { return nil, fmt.Errorf("unable to run local API: %s", err) } + err = apiServer.InitController() + if err != nil { + return nil, fmt.Errorf("unable to run local API: %s", err) + } log.Printf("Creating new API server") gin.SetMode(gin.TestMode) router, err := apiServer.Router() diff --git a/pkg/apiserver/controllers/controller.go b/pkg/apiserver/controllers/controller.go index ed23cba94..77c7be40d 100644 --- a/pkg/apiserver/controllers/controller.go +++ b/pkg/apiserver/controllers/controller.go @@ -5,6 +5,7 @@ import ( "github.com/alexliesenfeld/health" v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers/v1" "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/database" "github.com/crowdsecurity/crowdsec/pkg/models" "github.com/gin-gonic/gin" @@ -13,12 +14,13 @@ import ( ) type Controller struct { - Ectx context.Context - DBClient *database.Client - Router *gin.Engine - Profiles []*csconfig.ProfileCfg - CAPIChan chan []*models.Alert - Log *log.Logger + Ectx context.Context + DBClient *database.Client + Router *gin.Engine + Profiles []*csconfig.ProfileCfg + CAPIChan chan []*models.Alert + PluginChannel chan csplugin.ProfileAlert + Log *log.Logger } func (c *Controller) Init() error { @@ -49,7 +51,7 @@ func serveHealth() http.HandlerFunc { } func (c *Controller) NewV1() error { - handlerV1, err := v1.New(c.DBClient, c.Ectx, c.Profiles, c.CAPIChan) + handlerV1, err := v1.New(c.DBClient, c.Ectx, c.Profiles, c.CAPIChan, c.PluginChannel) if err != nil { return err } diff --git a/pkg/apiserver/controllers/v1/alerts.go b/pkg/apiserver/controllers/v1/alerts.go index 2892ccc5f..80228672e 100644 --- a/pkg/apiserver/controllers/v1/alerts.go +++ b/pkg/apiserver/controllers/v1/alerts.go @@ -9,6 +9,7 @@ import ( jwt "github.com/appleboy/gin-jwt/v2" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/csprofiles" "github.com/crowdsecurity/crowdsec/pkg/database/ent" "github.com/crowdsecurity/crowdsec/pkg/models" @@ -96,6 +97,15 @@ func FormatAlerts(result []*ent.Alert) models.AddAlertsRequest { return data } +func (c *Controller) sendAlertToPluginChannel(alert *models.Alert, profileID uint) { + select { + case c.PluginChannel <- csplugin.ProfileAlert{ProfileID: uint(profileID), Alert: alert}: + log.Debugf("alert sent to Plugin channel") + default: + log.Warningf("Cannot send alert to Plugin channel") + } +} + // CreateAlert : write received alerts in body to the database func (c *Controller) CreateAlert(gctx *gin.Context) { @@ -115,13 +125,36 @@ func (c *Controller) CreateAlert(gctx *gin.Context) { } for _, alert := range input { - if len(alert.Decisions) == 0 { - decisions, err := csprofiles.EvaluateProfiles(c.Profiles, alert) + if len(alert.Decisions) != 0 { + for pIdx, profile := range c.Profiles { + _, matched, err := csprofiles.EvaluateProfile(profile, alert) + if err != nil { + gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) + return + } + if !matched { + continue + } + c.sendAlertToPluginChannel(alert, uint(pIdx)) + } + continue + } + + for pIdx, profile := range c.Profiles { + profileDecisions, matched, err := csprofiles.EvaluateProfile(profile, alert) if err != nil { gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) return } - alert.Decisions = decisions + if !matched { + continue + } + alert.Decisions = append(alert.Decisions, profileDecisions...) + profileAlert := *alert + c.sendAlertToPluginChannel(&profileAlert, uint(pIdx)) + if profile.OnSuccess == "break" { + break + } } } @@ -135,11 +168,10 @@ func (c *Controller) CreateAlert(gctx *gin.Context) { } select { case c.CAPIChan <- input: - log.Debugf("alert send to CAPI channel") + log.Debugf("alert sent to CAPI channel") default: log.Warningf("Cannot send alert to Central API channel") } - gctx.JSON(http.StatusCreated, alerts) return } diff --git a/pkg/apiserver/controllers/v1/controller.go b/pkg/apiserver/controllers/v1/controller.go index 8a11ec84c..f6bbb3ce9 100644 --- a/pkg/apiserver/controllers/v1/controller.go +++ b/pkg/apiserver/controllers/v1/controller.go @@ -5,27 +5,30 @@ import ( middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1" "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/csplugin" "github.com/crowdsecurity/crowdsec/pkg/database" "github.com/crowdsecurity/crowdsec/pkg/models" ) type Controller struct { - Ectx context.Context - DBClient *database.Client - APIKeyHeader string - Middlewares *middlewares.Middlewares - Profiles []*csconfig.ProfileCfg - CAPIChan chan []*models.Alert + Ectx context.Context + DBClient *database.Client + APIKeyHeader string + Middlewares *middlewares.Middlewares + Profiles []*csconfig.ProfileCfg + CAPIChan chan []*models.Alert + PluginChannel chan csplugin.ProfileAlert } -func New(dbClient *database.Client, ctx context.Context, profiles []*csconfig.ProfileCfg, capiChan chan []*models.Alert) (*Controller, error) { +func New(dbClient *database.Client, ctx context.Context, profiles []*csconfig.ProfileCfg, capiChan chan []*models.Alert, pluginChannel chan csplugin.ProfileAlert) (*Controller, error) { var err error v1 := &Controller{ - Ectx: ctx, - DBClient: dbClient, - APIKeyHeader: middlewares.APIKeyHeader, - Profiles: profiles, - CAPIChan: capiChan, + Ectx: ctx, + DBClient: dbClient, + APIKeyHeader: middlewares.APIKeyHeader, + Profiles: profiles, + CAPIChan: capiChan, + PluginChannel: pluginChannel, } v1.Middlewares, err = middlewares.NewMiddlewares(dbClient) if err != nil { diff --git a/pkg/apiserver/decisions_test.go b/pkg/apiserver/decisions_test.go index 9d2917730..ada7fe467 100644 --- a/pkg/apiserver/decisions_test.go +++ b/pkg/apiserver/decisions_test.go @@ -43,15 +43,13 @@ func TestDeleteDecisionRange(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // delete by ip wrong w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?range=1.2.3.0/24", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String()) @@ -59,8 +57,7 @@ func TestDeleteDecisionRange(t *testing.T) { // delete by range w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String()) @@ -68,8 +65,7 @@ func TestDeleteDecisionRange(t *testing.T) { // delete by range : ensure it was already deleted w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?range=91.121.79.0/24", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String()) @@ -103,15 +99,13 @@ func TestDeleteDecisionFilter(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // delete by ip wrong w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?ip=1.2.3.4", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String()) @@ -119,8 +113,7 @@ func TestDeleteDecisionFilter(t *testing.T) { // delete by ip good w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?ip=91.121.79.179", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String()) @@ -128,8 +121,7 @@ func TestDeleteDecisionFilter(t *testing.T) { // delete by scope/value w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?scope=Ip&value=91.121.79.178", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String()) @@ -163,8 +155,7 @@ func TestGetDecisionFilters(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) APIKey, err := CreateTestBouncer() @@ -250,8 +241,7 @@ func TestGetDecision(t *testing.T) { } w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) APIKey, err := CreateTestBouncer() @@ -308,15 +298,13 @@ func TestDeleteDecisionByID(t *testing.T) { } w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // Delete alert with Invalid ID w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions/test", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) @@ -325,8 +313,7 @@ func TestDeleteDecisionByID(t *testing.T) { // Delete alert with ID that not exist w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions/100", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) @@ -335,8 +322,7 @@ func TestDeleteDecisionByID(t *testing.T) { // Delete alert with valid ID w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions/1", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) @@ -372,15 +358,13 @@ func TestDeleteDecision(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent))) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) // Delete alert with Invalid filter w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions?test=test", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 500, w.Code) @@ -389,8 +373,7 @@ func TestDeleteDecision(t *testing.T) { // Delete alert w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/v1/decisions", strings.NewReader("")) - req.Header.Add("User-Agent", UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token)) + AddAuthHeaders(req, loginResp) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) diff --git a/pkg/csconfig/config_paths.go b/pkg/csconfig/config_paths.go index 1dcb95d81..59be93ae6 100644 --- a/pkg/csconfig/config_paths.go +++ b/pkg/csconfig/config_paths.go @@ -13,6 +13,8 @@ type ConfigurationPaths struct { SimulationFilePath string `yaml:"simulation_path,omitempty"` HubIndexFile string `yaml:"index_path,omitempty"` //path of the .index.json HubDir string `yaml:"hub_dir,omitempty"` + PluginDir string `yaml:"plugin_dir,omitempty"` + NotificationDir string `yaml:"notification_dir,omitempty"` } func (c *Config) LoadConfigurationPaths() error { @@ -39,6 +41,8 @@ func (c *Config) LoadConfigurationPaths() error { &c.ConfigPaths.ConfigDir, &c.ConfigPaths.DataDir, &c.ConfigPaths.SimulationFilePath, + &c.ConfigPaths.PluginDir, + &c.ConfigPaths.NotificationDir, } for _, k := range configPathsCleanup { if *k == "" { diff --git a/pkg/csconfig/profiles.go b/pkg/csconfig/profiles.go index 9e4b3ff92..4cf790bfb 100644 --- a/pkg/csconfig/profiles.go +++ b/pkg/csconfig/profiles.go @@ -24,6 +24,7 @@ type ProfileCfg struct { Decisions []models.Decision `yaml:"decisions,omitempty"` OnSuccess string `yaml:"on_success,omitempty"` //continue or break OnFailure string `yaml:"on_failure,omitempty"` //continue or break + Notifications []string `yaml:"notifications,omitempty"` } func (c *LocalApiServerCfg) LoadProfiles() error { diff --git a/pkg/csplugin/broker.go b/pkg/csplugin/broker.go new file mode 100644 index 000000000..32cf48d2a --- /dev/null +++ b/pkg/csplugin/broker.go @@ -0,0 +1,444 @@ +package csplugin + +import ( + "context" + "fmt" + "io" + "io/fs" + "math" + "os" + "os/exec" + "os/user" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "text/template" + "time" + + "github.com/Masterminds/sprig" + "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/models" + "github.com/crowdsecurity/crowdsec/pkg/protobufs" + "github.com/crowdsecurity/crowdsec/pkg/types" + plugin "github.com/hashicorp/go-plugin" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "gopkg.in/tomb.v2" + "gopkg.in/yaml.v2" +) + +var testMode bool = false +var pluginMutex sync.Mutex + +const ( + PluginProtocolVersion uint = 1 + CrowdsecPluginKey string = "CROWDSEC_PLUGIN_KEY" +) + +type PluginBroker struct { + PluginChannel chan ProfileAlert + alertsByPluginName map[string][]*models.Alert + profileConfigs []*csconfig.ProfileCfg + pluginConfigByName map[string]PluginConfig + pluginMap map[string]plugin.Plugin + notificationConfigsByPluginType map[string][][]byte // "slack" -> []{config1, config2} + notificationPluginByName map[string]Notifier + watcher PluginWatcher + pluginKillMethods []func() +} + +// holder to determine where to dispatch config and how to format messages +type PluginConfig struct { + Type string `yaml:"type"` + Name string `yaml:"name"` + GroupWait time.Duration `yaml:"group_wait"` + GroupThreshold int `yaml:"group_threshold"` + MaxRetry int `yaml:"max_retry"` + TimeOut time.Duration `yaml:"timeout"` + + Format string `yaml:"format"` // specific to notification plugins + + Config map[string]interface{} `yaml:",inline"` //to keep the plugin-specific config + +} + +type ProfileAlert struct { + ProfileID uint + Alert *models.Alert +} + +func (pb *PluginBroker) Init(profileConfigs []*csconfig.ProfileCfg, configPaths *csconfig.ConfigurationPaths) error { + pb.PluginChannel = make(chan ProfileAlert) + pb.notificationConfigsByPluginType = make(map[string][][]byte) + pb.notificationPluginByName = make(map[string]Notifier) + pb.pluginMap = make(map[string]plugin.Plugin) + pb.pluginConfigByName = make(map[string]PluginConfig) + pb.alertsByPluginName = make(map[string][]*models.Alert) + pb.profileConfigs = profileConfigs + if err := pb.loadConfig(configPaths.NotificationDir); err != nil { + return errors.Wrap(err, "while loading plugin config") + } + if err := pb.loadPlugins(configPaths.PluginDir); err != nil { + return errors.Wrap(err, "while loading plugin") + } + pb.watcher = PluginWatcher{} + pb.watcher.Init(pb.pluginConfigByName, pb.alertsByPluginName) + return nil + +} + +func (pb *PluginBroker) Kill() { + for _, kill := range pb.pluginKillMethods { + kill() + } +} + +func (pb *PluginBroker) Run(tomb *tomb.Tomb) { + pb.watcher.Start(tomb) + for { + select { + case profileAlert := <-pb.PluginChannel: + pb.addProfileAlert(profileAlert) + + case pluginName := <-pb.watcher.PluginEvents: + // this can be ran in goroutine, but then locks will be needed + pluginMutex.Lock() + tmpAlerts := pb.alertsByPluginName[pluginName] + pb.alertsByPluginName[pluginName] = make([]*models.Alert, 0) + pluginMutex.Unlock() + go func() { + if err := pb.pushNotificationsToPlugin(pluginName, tmpAlerts); err != nil { + log.WithField("plugin:", pluginName).Error(err) + } + }() + + case <-tomb.Dying(): + log.Info("killing all plugins") + pb.Kill() + return + } + } +} +func (pb *PluginBroker) addProfileAlert(profileAlert ProfileAlert) { + for _, pluginName := range pb.profileConfigs[profileAlert.ProfileID].Notifications { + if _, ok := pb.pluginConfigByName[pluginName]; !ok { + log.Errorf("plugin %s is not configured properly.", pluginName) + continue + } + pluginMutex.Lock() + pb.alertsByPluginName[pluginName] = append(pb.alertsByPluginName[pluginName], profileAlert.Alert) + pluginMutex.Unlock() + pb.watcher.Inserts <- pluginName + } +} +func (pb *PluginBroker) profilesContainPlugin(pluginName string) bool { + for _, profileCfg := range pb.profileConfigs { + for _, name := range profileCfg.Notifications { + if pluginName == name { + return true + } + } + } + return false +} +func (pb *PluginBroker) loadConfig(path string) error { + files, err := listFilesAtPath(path) + if err != nil { + return err + } + for _, configFilePath := range files { + if !strings.HasSuffix(configFilePath, ".yaml") && !strings.HasSuffix(configFilePath, ".yml") { + continue + } + + pluginConfigs, err := parsePluginConfigFile(configFilePath) + if err != nil { + return errors.Wrapf(err, "got error while parsing %s", configFilePath) + } + for _, pluginConfig := range pluginConfigs { + if !pb.profilesContainPlugin(pluginConfig.Name) { + continue + } + setRequiredFields(&pluginConfig) + if _, ok := pb.pluginConfigByName[pluginConfig.Name]; ok { + log.Warnf("several configs for notification %s found ", pluginConfig.Name) + } + pb.pluginConfigByName[pluginConfig.Name] = pluginConfig + } + } + err = pb.verifyPluginConfigsWithProfile() + return err +} + +func (pb *PluginBroker) verifyPluginConfigsWithProfile() error { + for _, profileCfg := range pb.profileConfigs { + for _, pluginName := range profileCfg.Notifications { + if _, ok := pb.pluginConfigByName[pluginName]; !ok { + return fmt.Errorf("config file for plugin %s not found", pluginName) + } + } + } + return nil +} + +func (pb *PluginBroker) loadPlugins(path string) error { + binaryPaths, err := listFilesAtPath(path) + if err != nil { + return err + } + for _, binaryPath := range binaryPaths { + if err := pluginIsValid(binaryPath); err != nil { + return err + } + pType, pSubtype, err := getPluginTypeAndSubtypeFromPath(binaryPath) // eg pType="notification" , pSubtype="slack" + if err != nil { + return err + } + if pType != "notification" { + continue + } + + pluginClient, err := pb.loadNotificationPlugin(pSubtype, binaryPath) + if err != nil { + return err + } + for _, pc := range pb.pluginConfigByName { + if pc.Type != pSubtype { + continue + } + + data, err := yaml.Marshal(pc) + if err != nil { + return err + } + + _, err = pluginClient.Configure(context.Background(), &protobufs.Config{Config: data}) + if err != nil { + return errors.Wrapf(err, "while configuring %s", pc.Name) + } + log.Infof("registered plugin %s", pc.Name) + pb.notificationPluginByName[pc.Name] = pluginClient + } + } + return err +} + +func (pb *PluginBroker) loadNotificationPlugin(name string, binaryPath string) (Notifier, error) { + handshake, err := getHandshake() + if err != nil { + return nil, err + } + cmd := exec.Command(binaryPath) + cmd.SysProcAttr, err = getProccessAtr() + if err != nil { + return nil, errors.Wrap(err, "while getting process attributes") + } + pb.pluginMap[name] = &NotifierPlugin{} + l := log.New() + err = types.ConfigureLogger(l) + if err != nil { + return nil, err + } + logger := NewHCLogAdapter(l, "") + c := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshake, + Plugins: pb.pluginMap, + Cmd: cmd, + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: logger, + }) + client, err := c.Client() + if err != nil { + return nil, err + } + raw, err := client.Dispense(name) + if err != nil { + return nil, err + } + pb.pluginKillMethods = append(pb.pluginKillMethods, c.Kill) + return raw.(Notifier), nil +} + +func (pb *PluginBroker) pushNotificationsToPlugin(pluginName string, alerts []*models.Alert) error { + if len(alerts) == 0 { + return nil + } + + message, err := formatAlerts(pb.pluginConfigByName[pluginName].Format, alerts) + if err != nil { + return err + } + plugin := pb.notificationPluginByName[pluginName] + backoffDuration := time.Second + for i := 1; i <= pb.pluginConfigByName[pluginName].MaxRetry; i++ { + ctx, cancel := context.WithTimeout(context.Background(), pb.pluginConfigByName[pluginName].TimeOut) + defer cancel() + _, err = plugin.Notify( + ctx, + &protobufs.Notification{ + Text: message, + Name: pluginName, + }, + ) + if err == nil { + return err + } + log.WithField("plugin", pluginName).Errorf("%s error, retry num %d", err.Error(), i) + time.Sleep(backoffDuration) + backoffDuration *= 2 + } + + return err +} + +func parsePluginConfigFile(path string) ([]PluginConfig, error) { + parsedConfigs := make([]PluginConfig, 0) + yamlFile, err := os.Open(path) + if err != nil { + return parsedConfigs, errors.Wrapf(err, "while opening %s", path) + } + dec := yaml.NewDecoder(yamlFile) + for { + pc := PluginConfig{} + err = dec.Decode(&pc) + if err != nil { + if err == io.EOF { + break + } + log.Errorf("while decoding %s got error %s", path, err.Error()) + continue + } + parsedConfigs = append(parsedConfigs, pc) + } + return parsedConfigs, nil +} + +func setRequiredFields(pluginCfg *PluginConfig) { + if pluginCfg.MaxRetry == 0 { + pluginCfg.MaxRetry++ + } + + if pluginCfg.TimeOut == time.Second*0 { + pluginCfg.TimeOut = time.Second * 5 + } + + if pluginCfg.GroupWait == time.Second*0 { + pluginCfg.GroupWait = time.Second * 1 + } +} + +func pluginIsValid(path string) error { + if testMode { + return nil + } + var details fs.FileInfo + var err error + + // check if it exists + if details, err = os.Stat(path); err != nil { + return errors.Wrap(err, fmt.Sprintf("plugin at %s does not exist", path)) + } + + // check if it is owned by root + stat := details.Sys().(*syscall.Stat_t) + if stat.Uid != 0 || stat.Gid != 0 { + return fmt.Errorf("plugin at %s is not owned by root user and group", path) + } + + if (int(details.Mode()) & 2) != 0 { + return fmt.Errorf("plugin at %s is world writable, world writable plugins are invalid", path) + } + return nil +} + +// helper which gives paths to all files in the given directory non-recursively +func listFilesAtPath(path string) ([]string, error) { + filePaths := make([]string, 0) + files, err := os.ReadDir(path) + if err != nil { + return nil, err + } + for _, file := range files { + if file.IsDir() { + continue + } + filePaths = append(filePaths, filepath.Join(path, file.Name())) + } + return filePaths, nil +} + +func getPluginTypeAndSubtypeFromPath(path string) (string, string, error) { + pluginFileName := filepath.Base(path) + parts := strings.Split(pluginFileName, "-") + if len(parts) < 2 { + return "", "", fmt.Errorf("plugin name %s is invalid. Name should be like {type-name}", path) + } + return strings.Join(parts[:len(parts)-1], "-"), parts[len(parts)-1], nil +} + +func getProccessAtr() (*syscall.SysProcAttr, error) { + u, err := user.Lookup("nobody") + if err != nil { + return nil, err + } + g, err := user.LookupGroup("nogroup") + if err != nil { + return nil, err + } + uid, err := strconv.Atoi(u.Uid) + if err != nil { + return nil, err + } + if uid < 0 && uid > math.MaxUint32 { + return nil, fmt.Errorf("out of bound uid") + } + gid, err := strconv.Atoi(g.Gid) + if err != nil { + return nil, err + } + if gid < 0 && gid > math.MaxUint32 { + return nil, fmt.Errorf("out of bound gid") + } + return &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, + }, nil +} + +func getUUID() (string, error) { + if d, err := os.ReadFile("/proc/sys/kernel/random/uuid"); err != nil { + return "", err + } else { + return string(d), nil + } +} + +func getHandshake() (plugin.HandshakeConfig, error) { + uuid, err := getUUID() + if err != nil { + return plugin.HandshakeConfig{}, err + } + handshake := plugin.HandshakeConfig{ + ProtocolVersion: PluginProtocolVersion, + MagicCookieKey: CrowdsecPluginKey, + MagicCookieValue: uuid, + } + return handshake, nil +} + +func formatAlerts(format string, alerts []*models.Alert) (string, error) { + template, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(format) + if err != nil { + return "", err + } + b := new(strings.Builder) + err = template.Execute(b, alerts) + if err != nil { + return "", err + } + return b.String(), nil +} diff --git a/pkg/csplugin/broker_test.go b/pkg/csplugin/broker_test.go new file mode 100644 index 000000000..45d4db40f --- /dev/null +++ b/pkg/csplugin/broker_test.go @@ -0,0 +1,142 @@ +package csplugin + +import ( + "io/ioutil" + "log" + "os" + "path" + "reflect" + "testing" +) + +var testPath string + +func Test_getPluginNameAndTypeFromPath(t *testing.T) { + setUp() + defer tearDown() + type args struct { + path string + } + tests := []struct { + name string + args args + want string + want1 string + wantErr bool + }{ + { + name: "valid plugin name, single dash", + args: args{ + path: path.Join(testPath, "notification-gitter"), + }, + want: "notification", + want1: "gitter", + wantErr: false, + }, + { + name: "invalid plugin name", + args: args{ + path: "./tests/gitter", + }, + want: "", + want1: "", + wantErr: true, + }, + { + name: "valid plugin name, multiple dash", + args: args{ + path: "./tests/notification-instant-slack", + }, + want: "notification-instant", + want1: "slack", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := getPluginTypeAndSubtypeFromPath(tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("getPluginNameAndTypeFromPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getPluginNameAndTypeFromPath() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("getPluginNameAndTypeFromPath() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func Test_listFilesAtPath(t *testing.T) { + setUp() + defer tearDown() + type args struct { + path string + } + tests := []struct { + name string + args args + want []string + wantErr bool + }{ + { + name: "valid directory", + args: args{ + path: testPath, + }, + want: []string{ + path.Join(testPath, "notification-gitter"), + path.Join(testPath, "slack"), + }, + }, + { + name: "invalid directory", + args: args{ + path: "./foo/bar/", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := listFilesAtPath(tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("listFilesAtPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("listFilesAtPath() = %v, want %v", got, tt.want) + } + }) + } +} + +func setUp() { + testMode = true + dir, err := ioutil.TempDir("./", "cs_plugin_test") + if err != nil { + log.Fatal(err) + } + _, err = os.Create(path.Join(dir, "slack")) + if err != nil { + log.Fatal(err) + } + _, err = os.Create(path.Join(dir, "notification-gitter")) + if err != nil { + log.Fatal(err) + } + err = os.Mkdir(path.Join(dir, "dummy_dir"), 0666) + if err != nil { + log.Fatal(err) + } + testPath = dir +} + +func tearDown() { + err := os.RemoveAll(testPath) + if err != nil { + log.Fatal(err) + } +} diff --git a/pkg/csplugin/hclog_adapter.go b/pkg/csplugin/hclog_adapter.go new file mode 100644 index 000000000..253623c58 --- /dev/null +++ b/pkg/csplugin/hclog_adapter.go @@ -0,0 +1,213 @@ +// Copyright 2021 Workrise Technologies Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package csplugin + +import ( + "fmt" + "io" + "log" + "os" + "reflect" + + "github.com/hashicorp/go-hclog" + "github.com/sirupsen/logrus" +) + +// NewHCLogAdapter takes an instance of a Logrus logger and returns an hclog +// logger in the form of an HCLogAdapter. +func NewHCLogAdapter(l *logrus.Logger, name string) hclog.Logger { + return &HCLogAdapter{l, name, nil} +} + +// HCLogAdapter implements the hclog interface. Plugins use hclog to send +// log entries back to ephemeral-iam and this adapter allows for those logs +// to be handled by ephemeral-iam's Logrus logger. +type HCLogAdapter struct { + log *logrus.Logger + name string + + impliedArgs []interface{} +} + +func (h HCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) { + switch level { + case hclog.NoLevel: + return + case hclog.Trace: + h.Trace(msg, args...) + case hclog.Debug: + h.Debug(msg, args...) + case hclog.Info: + h.Info(msg, args...) + case hclog.Warn: + h.Warn(msg, args...) + case hclog.Error: + h.Error(msg, args...) + } +} + +func (h HCLogAdapter) Trace(msg string, args ...interface{}) { + h.log.WithFields(toLogrusFields(args)).Trace(msg) +} + +func (h HCLogAdapter) Debug(msg string, args ...interface{}) { + h.log.WithFields(toLogrusFields(args)).Debug(msg) +} + +func (h HCLogAdapter) Info(msg string, args ...interface{}) { + h.log.WithFields(toLogrusFields(args)).Info(msg) +} + +func (h HCLogAdapter) Warn(msg string, args ...interface{}) { + h.log.WithFields(toLogrusFields(args)).Warn(msg) +} + +func (h HCLogAdapter) Error(msg string, args ...interface{}) { + h.log.WithFields(toLogrusFields(args)).Error(msg) +} + +func (h HCLogAdapter) IsTrace() bool { + return h.log.GetLevel() >= logrus.TraceLevel +} + +func (h HCLogAdapter) IsDebug() bool { + return h.log.GetLevel() >= logrus.DebugLevel +} + +func (h HCLogAdapter) IsInfo() bool { + return h.log.GetLevel() >= logrus.InfoLevel +} + +func (h HCLogAdapter) IsWarn() bool { + return h.log.GetLevel() >= logrus.WarnLevel +} + +func (h HCLogAdapter) IsError() bool { + return h.log.GetLevel() >= logrus.ErrorLevel +} + +func (h HCLogAdapter) ImpliedArgs() []interface{} { + // Not supported. + return nil +} + +func (h HCLogAdapter) With(args ...interface{}) hclog.Logger { + return &h +} + +func (h HCLogAdapter) Name() string { + return h.name +} + +func (h HCLogAdapter) Named(name string) hclog.Logger { + return NewHCLogAdapter(h.log, name) +} + +func (h HCLogAdapter) ResetNamed(name string) hclog.Logger { + return &h +} + +func (h *HCLogAdapter) SetLevel(level hclog.Level) { + h.log.SetLevel(convertLevel(level)) +} + +func (h HCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { + if opts == nil { + opts = &hclog.StandardLoggerOptions{} + } + return log.New(h.StandardWriter(opts), "", 0) +} + +func (h HCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + return os.Stderr +} + +// convertLevel maps hclog levels to Logrus levels. +func convertLevel(level hclog.Level) logrus.Level { + switch level { + case hclog.NoLevel: + // Logrus does not have NoLevel, so use Info instead. + return logrus.InfoLevel + case hclog.Trace: + return logrus.TraceLevel + case hclog.Debug: + return logrus.DebugLevel + case hclog.Info: + return logrus.InfoLevel + case hclog.Warn: + return logrus.WarnLevel + case hclog.Error: + return logrus.ErrorLevel + default: + return logrus.InfoLevel + } +} + +// toLogrusFields takes a list of key/value pairs passed to the hclog logger +// and converts them to a map to be used as Logrus fields. +func toLogrusFields(kvPairs []interface{}) map[string]interface{} { + m := map[string]interface{}{} + if len(kvPairs) == 0 { + return m + } + + if len(kvPairs)%2 == 1 { + // There are an odd number of key/value pairs so append nil as the final value. + kvPairs = append(kvPairs, nil) + } + + for i := 0; i < len(kvPairs); i += 2 { + // hclog automatically adds the timestamp field, ignore it. + if kvPairs[i] != "timestamp" { + merge(m, kvPairs[i], kvPairs[i+1]) + } + } + return m +} + +// merge takes a key/value pair and converts them to strings then adds them to +// the dst map. +func merge(dst map[string]interface{}, k, v interface{}) { + var key string + + switch x := k.(type) { + case string: + key = x + case fmt.Stringer: + key = safeString(x) + default: + key = fmt.Sprint(x) + } + + dst[key] = v +} + +// safeString takes an interface that implements the String() function and calls it +// to attempt to convert it to a string. If a panic occurs, and it's caused by a +// nil pointer, the value will be set to "NULL". +func safeString(str fmt.Stringer) (s string) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { + s = "NULL" + } else { + panic(panicVal) + } + } + }() + + s = str.String() + return +} diff --git a/pkg/csplugin/notifier.go b/pkg/csplugin/notifier.go new file mode 100644 index 000000000..64a1e6e71 --- /dev/null +++ b/pkg/csplugin/notifier.go @@ -0,0 +1,59 @@ +package csplugin + +import ( + "context" + "fmt" + + "github.com/crowdsecurity/crowdsec/pkg/protobufs" + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +type Notifier interface { + Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) + Configure(ctx context.Context, cfg *protobufs.Config) (*protobufs.Empty, error) +} + +type NotifierPlugin struct { + plugin.Plugin + Impl Notifier +} + +type GRPCClient struct{ client protobufs.NotifierClient } + +func (m *GRPCClient) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) { + done := make(chan error) + go func() { + _, err := m.client.Notify( + context.Background(), &protobufs.Notification{Text: notification.Text, Name: notification.Name}, + ) + done <- err + }() + select { + case err := <-done: + return &protobufs.Empty{}, err + + case <-ctx.Done(): + return &protobufs.Empty{}, fmt.Errorf("timeout exceeded") + } +} + +func (m *GRPCClient) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) { + _, err := m.client.Configure( + context.Background(), config, + ) + return &protobufs.Empty{}, err +} + +type GRPCServer struct { + Impl Notifier +} + +func (p *NotifierPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + protobufs.RegisterNotifierServer(s, p.Impl) + return nil +} + +func (p *NotifierPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: protobufs.NewNotifierClient(c)}, nil +} diff --git a/pkg/csplugin/watcher.go b/pkg/csplugin/watcher.go new file mode 100644 index 000000000..7ace2911f --- /dev/null +++ b/pkg/csplugin/watcher.go @@ -0,0 +1,76 @@ +package csplugin + +import ( + "time" + + "github.com/crowdsecurity/crowdsec/pkg/models" + "gopkg.in/tomb.v2" +) + +type PluginWatcher struct { + PluginConfigByName map[string]PluginConfig + AlertCountByPluginName map[string]int + PluginEvents chan string + Inserts chan string + tomb *tomb.Tomb +} + +func (pw *PluginWatcher) Init(configs map[string]PluginConfig, alertsByPluginName map[string][]*models.Alert) { + pw.PluginConfigByName = configs + pw.PluginEvents = make(chan string) + pw.AlertCountByPluginName = make(map[string]int) + pw.Inserts = make(chan string) + for name := range alertsByPluginName { + pw.AlertCountByPluginName[name] = 0 + } +} + +func (pw *PluginWatcher) Start(tomb *tomb.Tomb) { + pw.tomb = tomb + for name := range pw.PluginConfigByName { + pname := name + pw.tomb.Go(func() error { + pw.watchPluginTicker(pname) + return nil + }) + } + + pw.tomb.Go(func() error { + pw.watchPluginAlertCounts() + return nil + }) +} + +func (pw *PluginWatcher) watchPluginTicker(pluginName string) { + if pw.PluginConfigByName[pluginName].GroupWait <= time.Second*0 { + return + } + ticker := time.NewTicker(pw.PluginConfigByName[pluginName].GroupWait) + for { + select { + case <-ticker.C: + pw.PluginEvents <- pluginName + + case <-pw.tomb.Dying(): + ticker.Stop() + return + } + } +} + +func (pw *PluginWatcher) watchPluginAlertCounts() { + for { + select { + case pluginName := <-pw.Inserts: + if threshold := pw.PluginConfigByName[pluginName].GroupThreshold; threshold > 0 { + pw.AlertCountByPluginName[pluginName]++ + if pw.AlertCountByPluginName[pluginName] >= threshold { + pw.PluginEvents <- pluginName + pw.AlertCountByPluginName[pluginName] = 0 + } + } + case <-pw.tomb.Dying(): + return + } + } +} diff --git a/pkg/csprofiles/csprofiles.go b/pkg/csprofiles/csprofiles.go index fdb8a0ed9..76c7592b9 100644 --- a/pkg/csprofiles/csprofiles.go +++ b/pkg/csprofiles/csprofiles.go @@ -57,10 +57,9 @@ func GenerateDecisionFromProfile(Profile *csconfig.ProfileCfg, Alert *models.Ale var clog *log.Entry -//EvaluateProfiles is going to evaluate an Alert against a set of profiles to generate Decisions -func EvaluateProfiles(Profiles []*csconfig.ProfileCfg, Alert *models.Alert) ([]*models.Decision, error) { +//EvaluateProfile is going to evaluate an Alert against a profile to generate Decisions +func EvaluateProfile(profile *csconfig.ProfileCfg, Alert *models.Alert) ([]*models.Decision, bool, error) { var decisions []*models.Decision - if clog == nil { xlog := log.New() if err := types.ConfigureLogger(xlog); err != nil { @@ -71,52 +70,39 @@ func EvaluateProfiles(Profiles []*csconfig.ProfileCfg, Alert *models.Alert) ([]* "type": "profile", }) } - - if !Alert.Remediation { - return nil, nil - } -PROFILE_LOOP: - for _, profile := range Profiles { - matched := false - for eIdx, expression := range profile.RuntimeFilters { - output, err := expr.Run(expression, exprhelpers.GetExprEnv(map[string]interface{}{"Alert": Alert})) - if err != nil { - log.Warningf("failed to run whitelist expr : %v", err) - return nil, errors.Wrapf(err, "while running expression %s", profile.Filters[eIdx]) + matched := false + for eIdx, expression := range profile.RuntimeFilters { + output, err := expr.Run(expression, exprhelpers.GetExprEnv(map[string]interface{}{"Alert": Alert})) + if err != nil { + log.Warningf("failed to run whitelist expr : %v", err) + return nil, matched, errors.Wrapf(err, "while running expression %s", profile.Filters[eIdx]) + } + switch out := output.(type) { + case bool: + if profile.Debug != nil && *profile.Debug { + profile.DebugFilters[eIdx].Run(clog, out, exprhelpers.GetExprEnv(map[string]interface{}{"Alert": Alert})) } - - switch out := output.(type) { - case bool: - if profile.Debug != nil && *profile.Debug { - profile.DebugFilters[eIdx].Run(clog, out, exprhelpers.GetExprEnv(map[string]interface{}{"Alert": Alert})) - } - if out { - matched = true - /*the expression matched, create the associated decision*/ - subdecisions, err := GenerateDecisionFromProfile(profile, Alert) - if err != nil { - return nil, errors.Wrapf(err, "while generating decision from profile %s", profile.Name) - } - - decisions = append(decisions, subdecisions...) - } else { - log.Debugf("Profile %s filter is unsuccessful", profile.Name) - if profile.OnFailure == "break" { - break PROFILE_LOOP - } + if out { + matched = true + /*the expression matched, create the associated decision*/ + subdecisions, err := GenerateDecisionFromProfile(profile, Alert) + if err != nil { + return nil, matched, errors.Wrapf(err, "while generating decision from profile %s", profile.Name) } - default: - return nil, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, profile.Filters[eIdx]) - + decisions = append(decisions, subdecisions...) + } else { + log.Debugf("Profile %s filter is unsuccessful", profile.Name) + if profile.OnFailure == "break" { + break + } } + default: + return nil, matched, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, profile.Filters[eIdx]) + } - if matched { - if profile.OnSuccess == "break" { - break PROFILE_LOOP - } - } + } - return decisions, nil + return decisions, matched, nil } diff --git a/pkg/csprofiles/csprofiles_test.go b/pkg/csprofiles/csprofiles_test.go new file mode 100644 index 000000000..645532a03 --- /dev/null +++ b/pkg/csprofiles/csprofiles_test.go @@ -0,0 +1,104 @@ +package csprofiles + +import ( + "fmt" + "reflect" + "testing" + + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/vm" + "github.com/crowdsecurity/crowdsec/pkg/csconfig" + "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" + "github.com/crowdsecurity/crowdsec/pkg/models" +) + +var ( + scope = "Country" + typ = "ban" + simulated = false + duration = "1h" + + value = "CH" + scenario = "ssh-bf" +) + +func TestEvaluateProfile(t *testing.T) { + type args struct { + profile *csconfig.ProfileCfg + Alert *models.Alert + } + tests := []struct { + name string + args args + expectedDecisionCount int // count of expected decisions + expectedMatchStatus bool + }{ + { + name: "simple pass single expr", + args: args{ + profile: &csconfig.ProfileCfg{ + Filters: []string{fmt.Sprintf("Alert.GetScenario() == \"%s\"", scenario)}, + RuntimeFilters: []*vm.Program{}, + }, + Alert: &models.Alert{Remediation: true, Scenario: &scenario}, + }, + expectedDecisionCount: 0, + expectedMatchStatus: true, + }, + { + name: "simple fail single expr", + args: args{ + profile: &csconfig.ProfileCfg{ + Filters: []string{"Alert.GetScenario() == \"Foo\""}, + RuntimeFilters: []*vm.Program{}, + }, + Alert: &models.Alert{Remediation: true}, + }, + expectedDecisionCount: 0, + expectedMatchStatus: false, + }, + { + name: "1 expr fail 1 expr pass should still eval to match", + args: args{ + profile: &csconfig.ProfileCfg{ + Filters: []string{"1==1", "1!=1"}, + RuntimeFilters: []*vm.Program{}, + }, + Alert: &models.Alert{Remediation: true}, + }, + expectedDecisionCount: 0, + expectedMatchStatus: true, + }, + { + name: "simple filter with 2 decision", + args: args{ + profile: &csconfig.ProfileCfg{ + Filters: []string{"1==1"}, + RuntimeFilters: []*vm.Program{}, + Decisions: []models.Decision{ + {Type: &typ, Scope: &scope, Simulated: &simulated, Duration: &duration}, + {Type: &typ, Scope: &scope, Simulated: &simulated, Duration: &duration}, + }, + }, + Alert: &models.Alert{Remediation: true, Scenario: &scenario, Source: &models.Source{Value: &value}}, + }, + expectedDecisionCount: 2, + expectedMatchStatus: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, filter := range tt.args.profile.Filters { + runtimeFilter, _ := expr.Compile(filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"Alert": &models.Alert{}}))) + tt.args.profile.RuntimeFilters = append(tt.args.profile.RuntimeFilters, runtimeFilter) + } + got, got1, _ := EvaluateProfile(tt.args.profile, tt.args.Alert) + if !reflect.DeepEqual(len(got), tt.expectedDecisionCount) { + t.Errorf("EvaluateProfile() got = %+v, want %+v", got, tt.expectedDecisionCount) + } + if got1 != tt.expectedMatchStatus { + t.Errorf("EvaluateProfile() got1 = %v, want %v", got1, tt.expectedMatchStatus) + } + }) + } +} diff --git a/pkg/protobufs/README.md b/pkg/protobufs/README.md new file mode 100644 index 000000000..5156d9a2a --- /dev/null +++ b/pkg/protobufs/README.md @@ -0,0 +1,8 @@ +To generate go code for the `notifier.proto` files, run : + +``` +protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + proto/alert.proto` +``` + diff --git a/pkg/protobufs/notifier.pb.go b/pkg/protobufs/notifier.pb.go new file mode 100644 index 000000000..b5dc81135 --- /dev/null +++ b/pkg/protobufs/notifier.pb.go @@ -0,0 +1,395 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.12.4 +// source: notifier.proto + +package protobufs + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Notification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Notification) Reset() { + *x = Notification{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{0} +} + +func (x *Notification) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *Notification) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{1} +} + +func (x *Config) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{2} +} + +var File_notifier_proto protoreflect.FileDescriptor + +var file_notifier_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x61, 0x0a, 0x08, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, + 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x0d, 0x5a, + 0x0b, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notifier_proto_rawDescOnce sync.Once + file_notifier_proto_rawDescData = file_notifier_proto_rawDesc +) + +func file_notifier_proto_rawDescGZIP() []byte { + file_notifier_proto_rawDescOnce.Do(func() { + file_notifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_notifier_proto_rawDescData) + }) + return file_notifier_proto_rawDescData +} + +var file_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_notifier_proto_goTypes = []interface{}{ + (*Notification)(nil), // 0: proto.Notification + (*Config)(nil), // 1: proto.Config + (*Empty)(nil), // 2: proto.Empty +} +var file_notifier_proto_depIdxs = []int32{ + 0, // 0: proto.Notifier.Notify:input_type -> proto.Notification + 1, // 1: proto.Notifier.Configure:input_type -> proto.Config + 2, // 2: proto.Notifier.Notify:output_type -> proto.Empty + 2, // 3: proto.Notifier.Configure:output_type -> proto.Empty + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notifier_proto_init() } +func file_notifier_proto_init() { + if File_notifier_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Notification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notifier_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notifier_proto_goTypes, + DependencyIndexes: file_notifier_proto_depIdxs, + MessageInfos: file_notifier_proto_msgTypes, + }.Build() + File_notifier_proto = out.File + file_notifier_proto_rawDesc = nil + file_notifier_proto_goTypes = nil + file_notifier_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// NotifierClient is the client API for Notifier service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type NotifierClient interface { + Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) + Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) +} + +type notifierClient struct { + cc grpc.ClientConnInterface +} + +func NewNotifierClient(cc grpc.ClientConnInterface) NotifierClient { + return ¬ifierClient{cc} +} + +func (c *notifierClient) Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Notify", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *notifierClient) Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Configure", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotifierServer is the server API for Notifier service. +type NotifierServer interface { + Notify(context.Context, *Notification) (*Empty, error) + Configure(context.Context, *Config) (*Empty, error) +} + +// UnimplementedNotifierServer can be embedded to have forward compatible implementations. +type UnimplementedNotifierServer struct { +} + +func (*UnimplementedNotifierServer) Notify(context.Context, *Notification) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented") +} +func (*UnimplementedNotifierServer) Configure(context.Context, *Config) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Configure not implemented") +} + +func RegisterNotifierServer(s *grpc.Server, srv NotifierServer) { + s.RegisterService(&_Notifier_serviceDesc, srv) +} + +func _Notifier_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Notification) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Notify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Notify", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Notify(ctx, req.(*Notification)) + } + return interceptor(ctx, in, info, handler) +} + +func _Notifier_Configure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Config) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Configure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Configure", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Configure(ctx, req.(*Config)) + } + return interceptor(ctx, in, info, handler) +} + +var _Notifier_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Notifier", + HandlerType: (*NotifierServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Notify", + Handler: _Notifier_Notify_Handler, + }, + { + MethodName: "Configure", + Handler: _Notifier_Configure_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notifier.proto", +} diff --git a/pkg/protobufs/notifier.proto b/pkg/protobufs/notifier.proto new file mode 100644 index 000000000..b1c98a11f --- /dev/null +++ b/pkg/protobufs/notifier.proto @@ -0,0 +1,19 @@ +syntax = "proto3" ; +package proto; +option go_package = ".;protobufs"; + +message Notification { + string text = 1 ; + string name = 2 ; +} + +message Config { + bytes config = 2 ; +} + +message Empty {} + +service Notifier { + rpc Notify(Notification) returns (Empty); + rpc Configure(Config) returns (Empty); +} \ No newline at end of file diff --git a/pkg/types/utils.go b/pkg/types/utils.go index cd3b1220c..a9e37024c 100644 --- a/pkg/types/utils.go +++ b/pkg/types/utils.go @@ -41,7 +41,6 @@ func SetDefaultLoggerConfig(cfgMode string, cfgFolder string, cfgLevel log.Level log.SetLevel(logLevel) logFormatter = &log.TextFormatter{TimestampFormat: "02-01-2006 15:04:05", FullTimestamp: true} log.SetFormatter(logFormatter) - return nil } diff --git a/plugins/notifications/http/LICENSE b/plugins/notifications/http/LICENSE new file mode 100644 index 000000000..912563863 --- /dev/null +++ b/plugins/notifications/http/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Crowdsec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/notifications/http/Makefile b/plugins/notifications/http/Makefile new file mode 100644 index 000000000..f59c61ad7 --- /dev/null +++ b/plugins/notifications/http/Makefile @@ -0,0 +1,16 @@ +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +BINARY_NAME=notification-http + +clean: + @rm -f $(BINARY_NAME) + +build: clean + @$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME) -v + +static: clean + $(GOBUILD) $(LD_OPTS_STATIC) -o $(BINARY_NAME) -v -a -tags netgo \ No newline at end of file diff --git a/plugins/notifications/http/go.mod b/plugins/notifications/http/go.mod new file mode 100644 index 000000000..100d550f0 --- /dev/null +++ b/plugins/notifications/http/go.mod @@ -0,0 +1,12 @@ +module github.com/crowdsecurity/http-plugin + +go 1.16 + +require ( + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-plugin v1.4.2 + github.com/sirupsen/logrus v1.8.1 + google.golang.org/grpc v1.39.0 + google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/plugins/notifications/http/go.sum b/plugins/notifications/http/go.sum new file mode 100644 index 000000000..d89612050 --- /dev/null +++ b/plugins/notifications/http/go.sum @@ -0,0 +1,145 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/plugins/notifications/http/http.yaml b/plugins/notifications/http/http.yaml new file mode 100644 index 000000000..92dfaaaea --- /dev/null +++ b/plugins/notifications/http/http.yaml @@ -0,0 +1,25 @@ +# Don't change this +type: http + +name: http_default # this must match with the registered plugin in the profile +log_level: info # Options include: trace, debug, info, warn, error, off + +format: | # This template receives list of models.Alert objects. The request body would contain this. + {{.|toJson}} + +url: # plugin will make requests to this url. Eg value https://www.example.com/ + +method: POST # eg either of "POST", "GET", "PUT" and other http verbs is valid value. + +# headers: +# Authorization: token 0x64312313 + +# skip_tls_verification: # either true or false. Default is false + +# group_wait: # duration to wait collecting alerts before sending to this plugin, eg "30s" + +# group_threshold: # if alerts exceed this, then the plugin will be sent the message. eg "10" + +# max_retry: # number of tries to attempt to send message to plugins in case of error. + +# timeout: # duration to wait for response from plugin before considering this attempt a failure. eg "10s" diff --git a/plugins/notifications/http/interface.go b/plugins/notifications/http/interface.go new file mode 100644 index 000000000..1ea19c075 --- /dev/null +++ b/plugins/notifications/http/interface.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + // This isn't required when using VersionedPlugins + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// KV is the interface that we're exposing as a plugin. +type Notifier interface { + Notify(ctx context.Context, notification *Notification) (*Empty, error) + Configure(ctx context.Context, config *Config) (*Empty, error) +} + +// This is the implementation of plugin.NotifierPlugin so we can serve/consume this. +type NotifierPlugin struct { + // GRPCPlugin must still implement the Plugin interface + plugin.Plugin + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl Notifier +} + +type GRPCClient struct{ client NotifierClient } + +func (m *GRPCClient) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + _, err := m.client.Notify(context.Background(), notification) + return &Empty{}, err +} + +func (m *GRPCClient) Configure(ctx context.Context, config *Config) (*Empty, error) { + _, err := m.client.Configure(context.Background(), config) + return &Empty{}, err +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl Notifier +} + +func (p *NotifierPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + RegisterNotifierServer(s, p.Impl) + return nil +} + +func (p *NotifierPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: NewNotifierClient(c)}, nil +} diff --git a/plugins/notifications/http/main.go b/plugins/notifications/http/main.go new file mode 100644 index 000000000..20bf4e5d8 --- /dev/null +++ b/plugins/notifications/http/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "context" + "crypto/tls" + "fmt" + "io/ioutil" + "net/http" + "os" + + "github.com/hashicorp/go-hclog" + plugin "github.com/hashicorp/go-plugin" + + "gopkg.in/yaml.v2" +) + +type PluginConfig struct { + Name string `yaml:"name"` + URL string `yaml:"url"` + Headers map[string]string `yaml:"headers"` + SkipTLSVerification bool `yaml:"skip_tls_verification"` + Method string `yaml:"method"` + LogLevel *string `yaml:"log_level"` +} + +type HTTPPlugin struct { + PluginConfigByName map[string]PluginConfig +} + +var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{ + Name: "http-plugin", + Level: hclog.LevelFromString("DEBUG"), + Output: os.Stderr, + JSONFormat: true, +}) + +func (s *HTTPPlugin) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + if _, ok := s.PluginConfigByName[notification.Name]; !ok { + return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) + } + cfg := s.PluginConfigByName[notification.Name] + if cfg.LogLevel != nil && *cfg.LogLevel != "" { + logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) + } else { + logger.SetLevel(hclog.Info) + } + + logger.Info(fmt.Sprintf("received signal for %s config", notification.Name)) + client := http.Client{} + + if cfg.SkipTLSVerification { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + + request, err := http.NewRequest(cfg.Method, cfg.URL, bytes.NewReader([]byte(notification.Text))) + if err != nil { + return nil, err + } + + for headerName, headerValue := range cfg.Headers { + request.Header.Add(headerName, headerValue) + } + logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, string(notification.Text))) + resp, err := client.Do(request) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, err + } + respData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &Empty{}, fmt.Errorf("failed to read response body got error %s", string(err.Error())) + } + logger.Debug(fmt.Sprintf("got response %s", string(respData))) + + return &Empty{}, nil +} + +func (s *HTTPPlugin) Configure(ctx context.Context, config *Config) (*Empty, error) { + d := PluginConfig{} + err := yaml.Unmarshal(config.Config, &d) + s.PluginConfigByName[d.Name] = d + return &Empty{}, err +} + +func main() { + var handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "CROWDSEC_PLUGIN_KEY", + MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), + } + + sp := &HTTPPlugin{PluginConfigByName: make(map[string]PluginConfig)} + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshake, + Plugins: map[string]plugin.Plugin{ + "http": &NotifierPlugin{ + Impl: sp, + }, + }, + GRPCServer: plugin.DefaultGRPCServer, + Logger: logger, + }) +} diff --git a/plugins/notifications/http/notifier.pb.go b/plugins/notifications/http/notifier.pb.go new file mode 100644 index 000000000..a7fd8f848 --- /dev/null +++ b/plugins/notifications/http/notifier.pb.go @@ -0,0 +1,394 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.12.4 +// source: notifier.proto + +package main + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Notification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Notification) Reset() { + *x = Notification{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{0} +} + +func (x *Notification) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *Notification) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{1} +} + +func (x *Config) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{2} +} + +var File_notifier_proto protoreflect.FileDescriptor + +var file_notifier_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x61, 0x0a, 0x08, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, + 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x08, 0x5a, + 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notifier_proto_rawDescOnce sync.Once + file_notifier_proto_rawDescData = file_notifier_proto_rawDesc +) + +func file_notifier_proto_rawDescGZIP() []byte { + file_notifier_proto_rawDescOnce.Do(func() { + file_notifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_notifier_proto_rawDescData) + }) + return file_notifier_proto_rawDescData +} + +var file_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_notifier_proto_goTypes = []interface{}{ + (*Notification)(nil), // 0: proto.Notification + (*Config)(nil), // 1: proto.Config + (*Empty)(nil), // 2: proto.Empty +} +var file_notifier_proto_depIdxs = []int32{ + 0, // 0: proto.Notifier.Notify:input_type -> proto.Notification + 1, // 1: proto.Notifier.Configure:input_type -> proto.Config + 2, // 2: proto.Notifier.Notify:output_type -> proto.Empty + 2, // 3: proto.Notifier.Configure:output_type -> proto.Empty + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notifier_proto_init() } +func file_notifier_proto_init() { + if File_notifier_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Notification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notifier_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notifier_proto_goTypes, + DependencyIndexes: file_notifier_proto_depIdxs, + MessageInfos: file_notifier_proto_msgTypes, + }.Build() + File_notifier_proto = out.File + file_notifier_proto_rawDesc = nil + file_notifier_proto_goTypes = nil + file_notifier_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// NotifierClient is the client API for Notifier service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type NotifierClient interface { + Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) + Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) +} + +type notifierClient struct { + cc grpc.ClientConnInterface +} + +func NewNotifierClient(cc grpc.ClientConnInterface) NotifierClient { + return ¬ifierClient{cc} +} + +func (c *notifierClient) Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Notify", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *notifierClient) Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Configure", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotifierServer is the server API for Notifier service. +type NotifierServer interface { + Notify(context.Context, *Notification) (*Empty, error) + Configure(context.Context, *Config) (*Empty, error) +} + +// UnimplementedNotifierServer can be embedded to have forward compatible implementations. +type UnimplementedNotifierServer struct { +} + +func (*UnimplementedNotifierServer) Notify(context.Context, *Notification) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented") +} +func (*UnimplementedNotifierServer) Configure(context.Context, *Config) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Configure not implemented") +} + +func RegisterNotifierServer(s *grpc.Server, srv NotifierServer) { + s.RegisterService(&_Notifier_serviceDesc, srv) +} + +func _Notifier_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Notification) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Notify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Notify", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Notify(ctx, req.(*Notification)) + } + return interceptor(ctx, in, info, handler) +} + +func _Notifier_Configure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Config) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Configure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Configure", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Configure(ctx, req.(*Config)) + } + return interceptor(ctx, in, info, handler) +} + +var _Notifier_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Notifier", + HandlerType: (*NotifierServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Notify", + Handler: _Notifier_Notify_Handler, + }, + { + MethodName: "Configure", + Handler: _Notifier_Configure_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notifier.proto", +} diff --git a/plugins/notifications/slack/LICENSE b/plugins/notifications/slack/LICENSE new file mode 100644 index 000000000..912563863 --- /dev/null +++ b/plugins/notifications/slack/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Crowdsec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/notifications/slack/Makefile b/plugins/notifications/slack/Makefile new file mode 100644 index 000000000..22c4064ea --- /dev/null +++ b/plugins/notifications/slack/Makefile @@ -0,0 +1,18 @@ + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +BINARY_NAME=notification-slack + +build: clean + @$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME) -v + +clean: + @rm -f $(BINARY_NAME) + + +static: clean + $(GOBUILD) $(LD_OPTS_STATIC) -o $(BINARY_NAME) -v -a -tags netgo \ No newline at end of file diff --git a/plugins/notifications/slack/go.mod b/plugins/notifications/slack/go.mod new file mode 100644 index 000000000..0bd99262f --- /dev/null +++ b/plugins/notifications/slack/go.mod @@ -0,0 +1,13 @@ +module github.com/crowdsecurity/slack-plugin + +go 1.16 + +require ( + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-plugin v1.4.2 + github.com/sirupsen/logrus v1.8.1 + github.com/slack-go/slack v0.9.2 + google.golang.org/grpc v1.39.0 + google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/plugins/notifications/slack/go.sum b/plugins/notifications/slack/go.sum new file mode 100644 index 000000000..8c1ffdaa8 --- /dev/null +++ b/plugins/notifications/slack/go.sum @@ -0,0 +1,152 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/slack-go/slack v0.9.2 h1:tjIrKKYUCOmWeEAktWShKW+3UjLTH/wmgmCkAGAf8wM= +github.com/slack-go/slack v0.9.2/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/plugins/notifications/slack/interface.go b/plugins/notifications/slack/interface.go new file mode 100644 index 000000000..1ea19c075 --- /dev/null +++ b/plugins/notifications/slack/interface.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + // This isn't required when using VersionedPlugins + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// KV is the interface that we're exposing as a plugin. +type Notifier interface { + Notify(ctx context.Context, notification *Notification) (*Empty, error) + Configure(ctx context.Context, config *Config) (*Empty, error) +} + +// This is the implementation of plugin.NotifierPlugin so we can serve/consume this. +type NotifierPlugin struct { + // GRPCPlugin must still implement the Plugin interface + plugin.Plugin + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl Notifier +} + +type GRPCClient struct{ client NotifierClient } + +func (m *GRPCClient) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + _, err := m.client.Notify(context.Background(), notification) + return &Empty{}, err +} + +func (m *GRPCClient) Configure(ctx context.Context, config *Config) (*Empty, error) { + _, err := m.client.Configure(context.Background(), config) + return &Empty{}, err +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl Notifier +} + +func (p *NotifierPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + RegisterNotifierServer(s, p.Impl) + return nil +} + +func (p *NotifierPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: NewNotifierClient(c)}, nil +} diff --git a/plugins/notifications/slack/notifier.pb.go b/plugins/notifications/slack/notifier.pb.go new file mode 100644 index 000000000..a7fd8f848 --- /dev/null +++ b/plugins/notifications/slack/notifier.pb.go @@ -0,0 +1,394 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.12.4 +// source: notifier.proto + +package main + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Notification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Notification) Reset() { + *x = Notification{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{0} +} + +func (x *Notification) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *Notification) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{1} +} + +func (x *Config) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{2} +} + +var File_notifier_proto protoreflect.FileDescriptor + +var file_notifier_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x61, 0x0a, 0x08, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, + 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x08, 0x5a, + 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notifier_proto_rawDescOnce sync.Once + file_notifier_proto_rawDescData = file_notifier_proto_rawDesc +) + +func file_notifier_proto_rawDescGZIP() []byte { + file_notifier_proto_rawDescOnce.Do(func() { + file_notifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_notifier_proto_rawDescData) + }) + return file_notifier_proto_rawDescData +} + +var file_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_notifier_proto_goTypes = []interface{}{ + (*Notification)(nil), // 0: proto.Notification + (*Config)(nil), // 1: proto.Config + (*Empty)(nil), // 2: proto.Empty +} +var file_notifier_proto_depIdxs = []int32{ + 0, // 0: proto.Notifier.Notify:input_type -> proto.Notification + 1, // 1: proto.Notifier.Configure:input_type -> proto.Config + 2, // 2: proto.Notifier.Notify:output_type -> proto.Empty + 2, // 3: proto.Notifier.Configure:output_type -> proto.Empty + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notifier_proto_init() } +func file_notifier_proto_init() { + if File_notifier_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Notification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notifier_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notifier_proto_goTypes, + DependencyIndexes: file_notifier_proto_depIdxs, + MessageInfos: file_notifier_proto_msgTypes, + }.Build() + File_notifier_proto = out.File + file_notifier_proto_rawDesc = nil + file_notifier_proto_goTypes = nil + file_notifier_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// NotifierClient is the client API for Notifier service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type NotifierClient interface { + Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) + Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) +} + +type notifierClient struct { + cc grpc.ClientConnInterface +} + +func NewNotifierClient(cc grpc.ClientConnInterface) NotifierClient { + return ¬ifierClient{cc} +} + +func (c *notifierClient) Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Notify", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *notifierClient) Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Configure", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotifierServer is the server API for Notifier service. +type NotifierServer interface { + Notify(context.Context, *Notification) (*Empty, error) + Configure(context.Context, *Config) (*Empty, error) +} + +// UnimplementedNotifierServer can be embedded to have forward compatible implementations. +type UnimplementedNotifierServer struct { +} + +func (*UnimplementedNotifierServer) Notify(context.Context, *Notification) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented") +} +func (*UnimplementedNotifierServer) Configure(context.Context, *Config) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Configure not implemented") +} + +func RegisterNotifierServer(s *grpc.Server, srv NotifierServer) { + s.RegisterService(&_Notifier_serviceDesc, srv) +} + +func _Notifier_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Notification) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Notify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Notify", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Notify(ctx, req.(*Notification)) + } + return interceptor(ctx, in, info, handler) +} + +func _Notifier_Configure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Config) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Configure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Configure", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Configure(ctx, req.(*Config)) + } + return interceptor(ctx, in, info, handler) +} + +var _Notifier_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Notifier", + HandlerType: (*NotifierServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Notify", + Handler: _Notifier_Notify_Handler, + }, + { + MethodName: "Configure", + Handler: _Notifier_Configure_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notifier.proto", +} diff --git a/plugins/notifications/slack/slack.yaml b/plugins/notifications/slack/slack.yaml new file mode 100644 index 000000000..e4a207917 --- /dev/null +++ b/plugins/notifications/slack/slack.yaml @@ -0,0 +1,27 @@ +# Don't change this +type: slack + +name: slack_default # this must match with the registered plugin in the profile +log_level: info # Options include: trace, debug, info, warn, error, off + +format: | # This template receives list of models.Alert objects + {{range . -}} + {{$alert := . -}} + {{range .Decisions -}} + {{if $alert.Source.Cn -}} + :flag-{{$alert.Source.Cn}}: will get {{.Type}} for next {{.Duration}} for triggering {{.Scenario}}. {{end}} + {{if not $alert.Source.Cn -}} + :pirate_flag: will get {{.Type}} for next {{.Duration}} for triggering {{.Scenario}}. {{end}} + {{end -}} + {{end -}} + + +webhook: + +# group_wait: # duration to wait collecting alerts before sending to this plugin, eg "30s" + +# group_threshold: # if alerts exceed this, then the plugin will be sent the message. eg "10" + +# max_retry: # number of tries to attempt to send message to plugins in case of error. + +# timeout: # duration to wait for response from plugin before considering this attempt a failure. eg "10s" diff --git a/plugins/notifications/slack/slack_plugin.go b/plugins/notifications/slack/slack_plugin.go new file mode 100644 index 000000000..1481f888b --- /dev/null +++ b/plugins/notifications/slack/slack_plugin.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/go-hclog" + plugin "github.com/hashicorp/go-plugin" + + "github.com/slack-go/slack" + "gopkg.in/yaml.v2" +) + +type PluginConfig struct { + Name string `yaml:"name"` + Webhook string `yaml:"webhook"` + LogLevel *string `yaml:"log_level"` +} +type Notify struct { + ConfigByName map[string]PluginConfig +} + +var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{ + Name: "slack-plugin", + Level: hclog.LevelFromString("DEBUG"), + Output: os.Stderr, + JSONFormat: true, +}) + +func (n *Notify) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + if _, ok := n.ConfigByName[notification.Name]; !ok { + return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) + } + cfg := n.ConfigByName[notification.Name] + if cfg.LogLevel != nil && *cfg.LogLevel != "" { + logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) + } else { + logger.SetLevel(hclog.Info) + } + + logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name)) + logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text)) + err := slack.PostWebhook(n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{ + Text: notification.Text, + }) + if err != nil { + logger.Error(err.Error()) + } + + return &Empty{}, err +} + +func (n *Notify) Configure(ctx context.Context, config *Config) (*Empty, error) { + d := PluginConfig{} + if err := yaml.Unmarshal(config.Config, &d); err != nil { + return nil, err + } + n.ConfigByName[d.Name] = d + return &Empty{}, nil +} + +func main() { + var handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "CROWDSEC_PLUGIN_KEY", + MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), + } + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshake, + Plugins: map[string]plugin.Plugin{ + "slack": &NotifierPlugin{ + Impl: &Notify{ConfigByName: make(map[string]PluginConfig)}, + }, + }, + GRPCServer: plugin.DefaultGRPCServer, + Logger: logger, + }) +} diff --git a/plugins/notifications/splunk/LICENSE b/plugins/notifications/splunk/LICENSE new file mode 100644 index 000000000..912563863 --- /dev/null +++ b/plugins/notifications/splunk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Crowdsec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/notifications/splunk/Makefile b/plugins/notifications/splunk/Makefile new file mode 100644 index 000000000..ca61ab620 --- /dev/null +++ b/plugins/notifications/splunk/Makefile @@ -0,0 +1,17 @@ + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +BINARY_NAME=notification-splunk + +build: clean + @$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME) -v + +clean: + @rm -f $(BINARY_NAME) + +static: clean + $(GOBUILD) $(LD_OPTS_STATIC) -o $(BINARY_NAME) -v -a -tags netgo \ No newline at end of file diff --git a/plugins/notifications/splunk/go.mod b/plugins/notifications/splunk/go.mod new file mode 100644 index 000000000..2eb463f31 --- /dev/null +++ b/plugins/notifications/splunk/go.mod @@ -0,0 +1,12 @@ +module github.com/crowdsecurity/splunk-plugin + +go 1.16 + +require ( + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-plugin v1.4.2 + github.com/sirupsen/logrus v1.8.1 + google.golang.org/grpc v1.39.0 + google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/plugins/notifications/splunk/go.sum b/plugins/notifications/splunk/go.sum new file mode 100644 index 000000000..d89612050 --- /dev/null +++ b/plugins/notifications/splunk/go.sum @@ -0,0 +1,145 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/plugins/notifications/splunk/interface.go b/plugins/notifications/splunk/interface.go new file mode 100644 index 000000000..1ea19c075 --- /dev/null +++ b/plugins/notifications/splunk/interface.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + // This isn't required when using VersionedPlugins + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// KV is the interface that we're exposing as a plugin. +type Notifier interface { + Notify(ctx context.Context, notification *Notification) (*Empty, error) + Configure(ctx context.Context, config *Config) (*Empty, error) +} + +// This is the implementation of plugin.NotifierPlugin so we can serve/consume this. +type NotifierPlugin struct { + // GRPCPlugin must still implement the Plugin interface + plugin.Plugin + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl Notifier +} + +type GRPCClient struct{ client NotifierClient } + +func (m *GRPCClient) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + _, err := m.client.Notify(context.Background(), notification) + return &Empty{}, err +} + +func (m *GRPCClient) Configure(ctx context.Context, config *Config) (*Empty, error) { + _, err := m.client.Configure(context.Background(), config) + return &Empty{}, err +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl Notifier +} + +func (p *NotifierPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + RegisterNotifierServer(s, p.Impl) + return nil +} + +func (p *NotifierPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: NewNotifierClient(c)}, nil +} diff --git a/plugins/notifications/splunk/main.go b/plugins/notifications/splunk/main.go new file mode 100644 index 000000000..e665925b0 --- /dev/null +++ b/plugins/notifications/splunk/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + + "github.com/hashicorp/go-hclog" + plugin "github.com/hashicorp/go-plugin" + + "gopkg.in/yaml.v2" +) + +var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{ + Name: "splunk-plugin", + Level: hclog.LevelFromString("DEBUG"), + Output: os.Stderr, + JSONFormat: true, +}) + +type PluginConfig struct { + Name string `yaml:"name"` + URL string `yaml:"url"` + Token string `yaml:"token"` + LogLevel *string `yaml:"log_level"` +} + +type Splunk struct { + PluginConfigByName map[string]PluginConfig + Client http.Client +} + +type Payload struct { + Event string `json:"event"` +} + +func (s *Splunk) Notify(ctx context.Context, notification *Notification) (*Empty, error) { + if _, ok := s.PluginConfigByName[notification.Name]; !ok { + return &Empty{}, fmt.Errorf("splunk invalid config name %s", notification.Name) + } + cfg := s.PluginConfigByName[notification.Name] + if cfg.LogLevel != nil && *cfg.LogLevel != "" { + logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) + } else { + logger.SetLevel(hclog.Info) + } + logger.Info(fmt.Sprintf("received notify signal for %s config", notification.Name)) + + p := Payload{Event: notification.Text} + data, err := json.Marshal(p) + if err != nil { + return &Empty{}, err + } + + req, err := http.NewRequest("POST", cfg.URL, strings.NewReader(string(data))) + if err != nil { + return &Empty{}, err + } + + req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token)) + logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL)) + resp, err := s.Client.Do(req) + if err != nil { + return &Empty{}, err + } + + if resp.StatusCode != 200 { + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &Empty{}, fmt.Errorf("got non 200 response and failed to read error %s", string(err.Error())) + } + return &Empty{}, fmt.Errorf("got non 200 response %s", string(content)) + } + respData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &Empty{}, fmt.Errorf("failed to read response body got error %s", string(err.Error())) + } + logger.Debug(fmt.Sprintf("got response %s", string(respData))) + return &Empty{}, nil +} + +func (s *Splunk) Configure(ctx context.Context, config *Config) (*Empty, error) { + d := PluginConfig{} + err := yaml.Unmarshal(config.Config, &d) + s.PluginConfigByName[d.Name] = d + return &Empty{}, err +} + +func main() { + var handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "CROWDSEC_PLUGIN_KEY", + MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + + sp := &Splunk{PluginConfigByName: make(map[string]PluginConfig), Client: *client} + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshake, + Plugins: map[string]plugin.Plugin{ + "splunk": &NotifierPlugin{ + Impl: sp, + }, + }, + GRPCServer: plugin.DefaultGRPCServer, + Logger: logger, + }) +} diff --git a/plugins/notifications/splunk/notifier.pb.go b/plugins/notifications/splunk/notifier.pb.go new file mode 100644 index 000000000..a7fd8f848 --- /dev/null +++ b/plugins/notifications/splunk/notifier.pb.go @@ -0,0 +1,394 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.12.4 +// source: notifier.proto + +package main + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Notification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *Notification) Reset() { + *x = Notification{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{0} +} + +func (x *Notification) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *Notification) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{1} +} + +func (x *Config) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_notifier_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_notifier_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_notifier_proto_rawDescGZIP(), []int{2} +} + +var File_notifier_proto protoreflect.FileDescriptor + +var file_notifier_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x36, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x61, 0x0a, 0x08, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, + 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x08, 0x5a, + 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notifier_proto_rawDescOnce sync.Once + file_notifier_proto_rawDescData = file_notifier_proto_rawDesc +) + +func file_notifier_proto_rawDescGZIP() []byte { + file_notifier_proto_rawDescOnce.Do(func() { + file_notifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_notifier_proto_rawDescData) + }) + return file_notifier_proto_rawDescData +} + +var file_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_notifier_proto_goTypes = []interface{}{ + (*Notification)(nil), // 0: proto.Notification + (*Config)(nil), // 1: proto.Config + (*Empty)(nil), // 2: proto.Empty +} +var file_notifier_proto_depIdxs = []int32{ + 0, // 0: proto.Notifier.Notify:input_type -> proto.Notification + 1, // 1: proto.Notifier.Configure:input_type -> proto.Config + 2, // 2: proto.Notifier.Notify:output_type -> proto.Empty + 2, // 3: proto.Notifier.Configure:output_type -> proto.Empty + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notifier_proto_init() } +func file_notifier_proto_init() { + if File_notifier_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Notification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notifier_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notifier_proto_goTypes, + DependencyIndexes: file_notifier_proto_depIdxs, + MessageInfos: file_notifier_proto_msgTypes, + }.Build() + File_notifier_proto = out.File + file_notifier_proto_rawDesc = nil + file_notifier_proto_goTypes = nil + file_notifier_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// NotifierClient is the client API for Notifier service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type NotifierClient interface { + Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) + Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) +} + +type notifierClient struct { + cc grpc.ClientConnInterface +} + +func NewNotifierClient(cc grpc.ClientConnInterface) NotifierClient { + return ¬ifierClient{cc} +} + +func (c *notifierClient) Notify(ctx context.Context, in *Notification, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Notify", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *notifierClient) Configure(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/proto.Notifier/Configure", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotifierServer is the server API for Notifier service. +type NotifierServer interface { + Notify(context.Context, *Notification) (*Empty, error) + Configure(context.Context, *Config) (*Empty, error) +} + +// UnimplementedNotifierServer can be embedded to have forward compatible implementations. +type UnimplementedNotifierServer struct { +} + +func (*UnimplementedNotifierServer) Notify(context.Context, *Notification) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented") +} +func (*UnimplementedNotifierServer) Configure(context.Context, *Config) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Configure not implemented") +} + +func RegisterNotifierServer(s *grpc.Server, srv NotifierServer) { + s.RegisterService(&_Notifier_serviceDesc, srv) +} + +func _Notifier_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Notification) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Notify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Notify", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Notify(ctx, req.(*Notification)) + } + return interceptor(ctx, in, info, handler) +} + +func _Notifier_Configure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Config) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifierServer).Configure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Notifier/Configure", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifierServer).Configure(ctx, req.(*Config)) + } + return interceptor(ctx, in, info, handler) +} + +var _Notifier_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Notifier", + HandlerType: (*NotifierServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Notify", + Handler: _Notifier_Notify_Handler, + }, + { + MethodName: "Configure", + Handler: _Notifier_Configure_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notifier.proto", +} diff --git a/plugins/notifications/splunk/splunk.yaml b/plugins/notifications/splunk/splunk.yaml new file mode 100644 index 000000000..ad8b2335e --- /dev/null +++ b/plugins/notifications/splunk/splunk.yaml @@ -0,0 +1,20 @@ +# Don't change this +type: splunk + +name: splunk_default # this must match with the registered plugin in the profile +log_level: info # Options include: trace, debug, info, warn, error, off + +format: | # This template receives list of models.Alert objects + {{.|toJson}} + +url: +token: + + +# group_wait: # duration to wait collecting alerts before sending to this plugin, eg "30s" + +# group_threshold: # if alerts exceed this, then the plugin will be sent the message. eg "10" + +# max_retry: # number of tries to attempt to send message to plugins in case of error. + +# timeout: # duration to wait for response from plugin before considering this attempt a failure. eg "10s" diff --git a/rpm/SPECS/crowdsec.spec b/rpm/SPECS/crowdsec.spec index ea92f2682..ae73e2ef3 100644 --- a/rpm/SPECS/crowdsec.spec +++ b/rpm/SPECS/crowdsec.spec @@ -47,6 +47,12 @@ mkdir -p %{buildroot}/etc/crowdsec/hub mkdir -p %{buildroot}/etc/crowdsec/patterns mkdir -p %{buildroot}%{_sharedstatedir}/%{name}/data mkdir -p %{buildroot}%{_presetdir} + +mkdir -p %{buildroot}%{_sharedstatedir}/%{name}/plugins +mkdir -p %{buildroot}%{_sysconfdir}/crowdsec/notifications/ + + + install -m 755 -D cmd/crowdsec/crowdsec %{buildroot}%{_bindir}/%{name} install -m 755 -D cmd/crowdsec-cli/cscli %{buildroot}%{_bindir}/cscli install -m 755 -D wizard.sh %{buildroot}/usr/share/crowdsec/wizard.sh @@ -57,6 +63,16 @@ install -m 644 -D config/simulation.yaml %{buildroot}%{_sysconfdir}/crowdsec install -m 644 -D config/profiles.yaml %{buildroot}%{_sysconfdir}/crowdsec install -m 644 -D %{SOURCE1} %{buildroot}%{_presetdir} +install -m 551 plugins/notifications/slack/notification-slack %{buildroot}%{_sharedstatedir}/%{name}/plugins/ +install -m 551 plugins/notifications/http/notification-http %{buildroot}%{_sharedstatedir}/%{name}/plugins/ +install -m 551 plugins/notifications/splunk/notification-splunk %{buildroot}%{_sharedstatedir}/%{name}/plugins/ + +install -m 644 plugins/notifications/slack/slack.yaml %{buildroot}%{_sysconfdir}/crowdsec/notifications/ +install -m 644 plugins/notifications/http/http.yaml %{buildroot}%{_sysconfdir}/crowdsec/notifications/ +install -m 644 plugins/notifications/splunk/splunk.yaml %{buildroot}%{_sysconfdir}/crowdsec/notifications/ + + + %clean rm -rf %{buildroot} diff --git a/wizard.sh b/wizard.sh index a1521470b..26c39fe3c 100755 --- a/wizard.sh +++ b/wizard.sh @@ -23,7 +23,7 @@ CROWDSEC_PATH="/etc/crowdsec" CROWDSEC_CONFIG_PATH="${CROWDSEC_PATH}" CROWDSEC_LOG_FILE="/var/log/crowdsec.log" LAPI_LOG_FILE="/var/log/crowdsec_api.log" - +CROWDSEC_PLUGIN_DIR="/var/lib/crowdsec/plugins/" CROWDSEC_BIN="./cmd/crowdsec/crowdsec" CSCLI_BIN="./cmd/crowdsec-cli/cscli" @@ -64,6 +64,15 @@ telnet smb ' + +HTTP_PLUGIN_BINARY="./plugins/notifications/http/notification-http" +SLACK_PLUGIN_BINARY="./plugins/notifications/slack/notification-slack" +SPLUNK_PLUGIN_BINARY="./plugins/notifications/splunk/notification-splunk" + +HTTP_PLUGIN_CONFIG="./plugins/notifications/http/http.yaml" +SLACK_PLUGIN_CONFIG="./plugins/notifications/slack/slack.yaml" +SPLUNK_PLUGIN_CONFIG="./plugins/notifications/splunk/splunk.yaml" + BACKUP_DIR=$(mktemp -d) rm -rf $BACKUP_DIR @@ -451,6 +460,11 @@ install_bins() { log_dbg "Installing crowdsec binaries" install -v -m 755 -D "${CROWDSEC_BIN}" "${CROWDSEC_BIN_INSTALLED}" 1> /dev/null || exit install -v -m 755 -D "${CSCLI_BIN}" "${CSCLI_BIN_INSTALLED}" 1> /dev/null || exit + systemctl is-active --quiet crowdsec + if [ $? -eq 0 ]; then + systemctl stop crowdsec + fi + install_plugins symlink_bins } @@ -469,6 +483,24 @@ delete_bins() { rm -f ${CSCLI_BIN_INSTALLED} } +delete_plugins() { + rm -rf ${CROWDSEC_PLUGIN_DIR} +} + +install_plugins(){ + mkdir -p ${CROWDSEC_PLUGIN_DIR} + mkdir -p /etc/crowdsec/notifications + + cp ${SLACK_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR} + cp -n ${SLACK_PLUGIN_CONFIG} /etc/crowdsec/notifications + + cp ${SPLUNK_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR} + cp -n ${SPLUNK_PLUGIN_CONFIG} /etc/crowdsec/notifications + + cp ${HTTP_PLUGIN_BINARY} ${CROWDSEC_PLUGIN_DIR} + cp -n ${HTTP_PLUGIN_CONFIG} /etc/crowdsec/notifications +} + check_running_bouncers() { #when uninstalling, check if user still has bouncers BOUNCERS_COUNT=$(${CSCLI_BIN} bouncers list -o=json | jq '. | length')