Revamp unit tests (#1368)
* Revamp unit tests * Increase coverage * Use go-acc to get cross packages coverage Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
This commit is contained in:
parent
3f24bcdbcf
commit
d8dc01cd94
23 changed files with 8536 additions and 989 deletions
6
.github/workflows/ci_go-test.yml
vendored
6
.github/workflows/ci_go-test.yml
vendored
|
@ -62,11 +62,9 @@ jobs:
|
|||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: make build && go get -u github.com/jandelgado/gcov2lcov
|
||||
- name: Build package
|
||||
run: make package
|
||||
run: make build && go get -u github.com/jandelgado/gcov2lcov && go get -u github.com/ory/go-acc
|
||||
- name: All tests
|
||||
run: go test -coverprofile=coverage.out -covermode=atomic ./...
|
||||
run: go run github.com/ory/go-acc ./... -o coverage.out --ignore database,notifications,protobufs,cwversion,cstest,models
|
||||
- name: gcov2lcov
|
||||
uses: jandelgado/gcov2lcov-action@v1.0.2
|
||||
with:
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cstest"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -63,7 +63,7 @@ cscli explain --dsn "file://myfile.log" --type nginx
|
|||
log.Fatalf("unable to get absolue path of '%s', exiting", logFile)
|
||||
}
|
||||
dsn = fmt.Sprintf("file://%s", absolutePath)
|
||||
lineCount := getLineCountForFile(absolutePath)
|
||||
lineCount := types.GetLineCountForFile(absolutePath)
|
||||
if lineCount > 100 {
|
||||
log.Warnf("log file contains %d lines. This may take lot of resources.", lineCount)
|
||||
}
|
||||
|
@ -112,17 +112,3 @@ cscli explain --dsn "file://myfile.log" --type nginx
|
|||
|
||||
return cmdExplain
|
||||
}
|
||||
|
||||
func getLineCountForFile(filepath string) int {
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to open log file %s", filepath)
|
||||
}
|
||||
defer f.Close()
|
||||
lc := 0
|
||||
fs := bufio.NewScanner(f)
|
||||
for fs.Scan() {
|
||||
lc++
|
||||
}
|
||||
return lc
|
||||
}
|
||||
|
|
3
go.mod
3
go.mod
|
@ -38,6 +38,7 @@ require (
|
|||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/influxdata/go-syslog/v3 v3.0.0
|
||||
github.com/jackc/pgx/v4 v4.14.1
|
||||
github.com/jarcoal/httpmock v1.1.0
|
||||
github.com/jszwec/csvutil v1.5.1
|
||||
github.com/lib/pq v1.10.4
|
||||
github.com/mattn/go-sqlite3 v1.14.10
|
||||
|
@ -148,7 +149,7 @@ require (
|
|||
go.mongodb.org/mongo-driver v1.4.4 // indirect
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220317022123-2c4bbad7e934 // indirect
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -610,6 +610,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
|
|||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE=
|
||||
github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
|
@ -1253,8 +1255,8 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220317022123-2c4bbad7e934 h1:GwUTNnIS5asZGjc34dMBLO/LLp4kEvyZr/8wlQs1Bt8=
|
||||
golang.org/x/sys v0.0.0-20220317022123-2c4bbad7e934/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 h1:saXMvIOKvRFwbOMicHXr0B1uwoxq9dGmLe5ExMES6c4=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||
|
|
|
@ -63,8 +63,11 @@ func NewClient(config *Config) (*ApiClient, error) {
|
|||
func NewDefaultClient(URL *url.URL, prefix string, userAgent string, client *http.Client) (*ApiClient, error) {
|
||||
if client == nil {
|
||||
client = &http.Client{}
|
||||
if ht, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
ht.TLSClientConfig = &tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
||||
client.Transport = ht
|
||||
}
|
||||
}
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: InsecureSkipVerify}
|
||||
c := &ApiClient{client: client, BaseURL: URL, UserAgent: userAgent, URLPrefix: prefix}
|
||||
c.common.client = c
|
||||
c.Decisions = (*DecisionsService)(&c.common)
|
||||
|
|
|
@ -3,13 +3,11 @@ package apiserver
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
|
@ -19,6 +17,48 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type LAPI struct {
|
||||
router *gin.Engine
|
||||
loginResp models.WatcherAuthResponse
|
||||
bouncerKey string
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func SetupLAPITest(t *testing.T) LAPI {
|
||||
t.Helper()
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
APIKey, err := CreateTestBouncer()
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
return LAPI{
|
||||
router: router,
|
||||
loginResp: loginResp,
|
||||
bouncerKey: APIKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LAPI) InsertAlertFromFile(path string) *httptest.ResponseRecorder {
|
||||
alertReader := GetAlertReaderFromFile(path)
|
||||
return l.RecordResponse("POST", "/v1/alerts", alertReader)
|
||||
}
|
||||
|
||||
func (l *LAPI) RecordResponse(verb string, url string, body *strings.Reader) *httptest.ResponseRecorder {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
if err != nil {
|
||||
l.t.Fatal(err)
|
||||
}
|
||||
req.Header.Add("X-Api-Key", l.bouncerKey)
|
||||
AddAuthHeaders(req, l.loginResp)
|
||||
l.router.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func InitMachineTest() (*gin.Engine, models.WatcherAuthResponse, error) {
|
||||
router, err := NewAPITest()
|
||||
if err != nil {
|
||||
|
@ -61,82 +101,40 @@ func AddAuthHeaders(request *http.Request, authResponse models.WatcherAuthRespon
|
|||
}
|
||||
|
||||
func TestSimulatedAlert(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_minibulk+simul.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
lapi := SetupLAPITest(t)
|
||||
lapi.InsertAlertFromFile("./tests/alert_minibulk+simul.json")
|
||||
alertContent := GetAlertReaderFromFile("./tests/alert_minibulk+simul.json")
|
||||
//exclude decision in simulation mode
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?simulated=false", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("GET", "/v1/alerts?simulated=false", alertContent)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
||||
assert.NotContains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
||||
//include decision in simulation mode
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?simulated=true", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=true", alertContent)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over `)
|
||||
assert.Contains(t, w.Body.String(), `"message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over `)
|
||||
}
|
||||
|
||||
func TestCreateAlert(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
lapi := SetupLAPITest(t)
|
||||
// Create Alert with invalid format
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader("test"))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("POST", "/v1/alerts", strings.NewReader("test"))
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"invalid character 'e' in literal true (expecting 'r')\"}", w.Body.String())
|
||||
|
||||
// Create Alert with invalid input
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/invalidAlert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
alertContent := GetAlertReaderFromFile("./tests/invalidAlert_sample.json")
|
||||
|
||||
w = lapi.RecordResponse("POST", "/v1/alerts", alertContent)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"validation failure list:\\n0.scenario in body is required\\n0.scenario_hash in body is required\\n0.scenario_version in body is required\\n0.simulated in body is required\\n0.source in body is required\"}", w.Body.String())
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err = ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent = string(alertContentBytes)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "[\"1\"]", w.Body.String())
|
||||
}
|
||||
|
@ -154,12 +152,7 @@ func TestCreateAlertChannels(t *testing.T) {
|
|||
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)
|
||||
lapi := LAPI{router: apiServer.router, loginResp: loginResp}
|
||||
|
||||
var pd csplugin.ProfileAlert
|
||||
var wg sync.WaitGroup
|
||||
|
@ -170,389 +163,248 @@ func TestCreateAlertChannels(t *testing.T) {
|
|||
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
|
||||
}
|
||||
}()
|
||||
go lapi.InsertAlertFromFile("./tests/alert_ssh-bf.json")
|
||||
wg.Wait()
|
||||
assert.Equal(t, len(pd.Alert.Decisions), 1)
|
||||
apiServer.Close()
|
||||
}
|
||||
|
||||
func TestAlertListFilters(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_ssh-bf.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
//create one alert
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi := SetupLAPITest(t)
|
||||
lapi.InsertAlertFromFile("./tests/alert_ssh-bf.json")
|
||||
alertContent := GetAlertReaderFromFile("./tests/alert_ssh-bf.json")
|
||||
|
||||
//bad filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?test=test", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("GET", "/v1/alerts?test=test", alertContent)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String())
|
||||
|
||||
//get without filters
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
//check alert and decision
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test decision_type filter (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?decision_type=ban", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?decision_type=ban", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test decision_type filter (bad value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?decision_type=ratata", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?decision_type=ratata", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test scope (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?scope=Ip", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?scope=Ip", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test scope (bad value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?scope=rarara", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?scope=rarara", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test scenario (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?scenario=crowdsecurity/ssh-bf", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test scenario (bad value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?scenario=crowdsecurity/nope", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?scenario=crowdsecurity/nope", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test ip (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?ip=91.121.79.195", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=91.121.79.195", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test ip (bad value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?ip=99.122.77.195", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=99.122.77.195", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test ip (invalid value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?ip=gruueq", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?ip=gruueq", emptyBody)
|
||||
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())
|
||||
|
||||
//test range (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?range=91.121.79.0/24&contains=false", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=91.121.79.0/24&contains=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test range
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?range=99.122.77.0/24&contains=false", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=99.122.77.0/24&contains=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test range (invalid value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?range=ratata", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?range=ratata", emptyBody)
|
||||
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())
|
||||
|
||||
//test since (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?since=1h", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1h", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test since (ok but yelds no results)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?since=1ns", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1ns", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test since (invalid value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?since=1zuzu", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?since=1zuzu", emptyBody)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
||||
|
||||
//test until (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?until=1ns", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1ns", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test until (ok but no return)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?until=1m", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1m", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test until (invalid value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?until=1zuzu", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?until=1zuzu", emptyBody)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `{"message":"while parsing duration: time: unknown unit`)
|
||||
|
||||
//test simulated (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?simulated=true", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=true", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test simulated (ok)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?simulated=false", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?simulated=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test has active decision
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=true", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=true", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
|
||||
assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
|
||||
|
||||
//test has active decision
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=false", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "null", w.Body.String())
|
||||
|
||||
//test has active decision (invalid value)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?has_active_decision=ratatqata", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts?has_active_decision=ratatqata", emptyBody)
|
||||
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())
|
||||
|
||||
}
|
||||
|
||||
func TestAlertBulkInsert(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
lapi := SetupLAPITest(t)
|
||||
//insert a bulk of 20 alerts to trigger bulk insert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_bulk.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
lapi.InsertAlertFromFile("./tests/alert_bulk.json")
|
||||
alertContent := GetAlertReaderFromFile("./tests/alert_bulk.json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
w := lapi.RecordResponse("GET", "/v1/alerts", alertContent)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestListAlert(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
lapi := SetupLAPITest(t)
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
// List Alert with invalid filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts?test=test", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("GET", "/v1/alerts?test=test", emptyBody)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"Filter parameter 'test' is unknown (=test): invalid filter\"}", w.Body.String())
|
||||
|
||||
// List Alert
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/alerts", nil)
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/alerts", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "crowdsecurity/test")
|
||||
}
|
||||
|
||||
func TestCreateAlertErrors(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
lapi := SetupLAPITest(t)
|
||||
alertContent := GetAlertReaderFromFile("./tests/alert_sample.json")
|
||||
|
||||
//test invalid bearer
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", alertContent)
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "ratata"))
|
||||
router.ServeHTTP(w, req)
|
||||
lapi.router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 401, w.Code)
|
||||
|
||||
//test invalid bearer
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
req, _ = http.NewRequest("POST", "/v1/alerts", alertContent)
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token+"s"))
|
||||
router.ServeHTTP(w, req)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", lapi.loginResp.Token+"s"))
|
||||
lapi.router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 401, w.Code)
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteAlert(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi := SetupLAPITest(t)
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
// Fail Delete Alert
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/alerts", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/v1/alerts", strings.NewReader(""))
|
||||
AddAuthHeaders(req, lapi.loginResp)
|
||||
req.RemoteAddr = "127.0.0.2:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
lapi.router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 403, w.Code)
|
||||
assert.Equal(t, `{"message":"access forbidden from this IP (127.0.0.2)"}`, w.Body.String())
|
||||
|
||||
// Delete Alert
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/alerts", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
AddAuthHeaders(req, lapi.loginResp)
|
||||
req.RemoteAddr = "127.0.0.1:4242"
|
||||
router.ServeHTTP(w, req)
|
||||
lapi.router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||
}
|
||||
|
@ -579,17 +431,10 @@ func TestDeleteAlertTrustedIPS(t *testing.T) {
|
|||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
insertAlert := func() {
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alertContent := string(alertContentBytes)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(alertContent))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi := LAPI{
|
||||
router: router,
|
||||
loginResp: loginResp,
|
||||
t: t,
|
||||
}
|
||||
|
||||
assertAlertDeleteFailedFromIP := func(ip string) {
|
||||
|
@ -598,6 +443,7 @@ func TestDeleteAlertTrustedIPS(t *testing.T) {
|
|||
|
||||
AddAuthHeaders(req, loginResp)
|
||||
req.RemoteAddr = ip + ":1234"
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 403, w.Code)
|
||||
assert.Contains(t, w.Body.String(), fmt.Sprintf(`{"message":"access forbidden from this IP (%s)"}`, ip))
|
||||
|
@ -608,23 +454,24 @@ func TestDeleteAlertTrustedIPS(t *testing.T) {
|
|||
req, _ := http.NewRequest("DELETE", "/v1/alerts", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
req.RemoteAddr = ip + ":1234"
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||
}
|
||||
|
||||
insertAlert()
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assertAlertDeleteFailedFromIP("4.3.2.1")
|
||||
assertAlertDeletedFromIP("1.2.3.4")
|
||||
|
||||
insertAlert()
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assertAlertDeletedFromIP("1.2.4.0")
|
||||
insertAlert()
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assertAlertDeletedFromIP("1.2.4.1")
|
||||
insertAlert()
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assertAlertDeletedFromIP("1.2.4.255")
|
||||
|
||||
insertAlert()
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
assertAlertDeletedFromIP("127.0.0.1")
|
||||
|
||||
}
|
||||
|
|
|
@ -24,12 +24,16 @@ import (
|
|||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
PullInterval = "2h"
|
||||
PushInterval = "30s"
|
||||
MetricsInterval = "30m"
|
||||
var (
|
||||
PullInterval = time.Hour * 2
|
||||
PushInterval = time.Second * 30
|
||||
MetricsInterval = time.Minute * 30
|
||||
)
|
||||
|
||||
var SCOPE_CAPI string = "CAPI"
|
||||
var SCOPE_CAPI_ALIAS string = "crowdsecurity/community-blocklist" //we don't use "CAPI" directly, to make it less confusing for the user
|
||||
var SCOPE_LISTS string = "lists"
|
||||
|
||||
type apic struct {
|
||||
pullInterval time.Duration
|
||||
pushInterval time.Duration
|
||||
|
@ -47,15 +51,6 @@ type apic struct {
|
|||
consoleConfig *csconfig.ConsoleConfig
|
||||
}
|
||||
|
||||
func IsInSlice(a string, b []string) bool {
|
||||
for _, v := range b {
|
||||
if a == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *apic) FetchScenariosListFromDB() ([]string, error) {
|
||||
scenarios := make([]string, 0)
|
||||
machines, err := a.dbClient.ListMachines()
|
||||
|
@ -67,7 +62,7 @@ func (a *apic) FetchScenariosListFromDB() ([]string, error) {
|
|||
machineScenarios := strings.Split(v.Scenarios, ",")
|
||||
log.Debugf("%d scenarios for machine %d", len(machineScenarios), v.ID)
|
||||
for _, sv := range machineScenarios {
|
||||
if !IsInSlice(sv, scenarios) && sv != "" {
|
||||
if !types.InSlice(sv, scenarios) && sv != "" {
|
||||
scenarios = append(scenarios, sv)
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +71,7 @@ func (a *apic) FetchScenariosListFromDB() ([]string, error) {
|
|||
return scenarios, nil
|
||||
}
|
||||
|
||||
func AlertToSignal(alert *models.Alert, scenarioTrust string) *models.AddSignalsRequestItem {
|
||||
func alertToSignal(alert *models.Alert, scenarioTrust string) *models.AddSignalsRequestItem {
|
||||
return &models.AddSignalsRequestItem{
|
||||
Message: alert.Message,
|
||||
Scenario: alert.Scenario,
|
||||
|
@ -94,29 +89,19 @@ func AlertToSignal(alert *models.Alert, scenarioTrust string) *models.AddSignals
|
|||
func NewAPIC(config *csconfig.OnlineApiClientCfg, dbClient *database.Client, consoleConfig *csconfig.ConsoleConfig) (*apic, error) {
|
||||
var err error
|
||||
ret := &apic{
|
||||
alertToPush: make(chan []*models.Alert),
|
||||
dbClient: dbClient,
|
||||
mu: sync.Mutex{},
|
||||
startup: true,
|
||||
credentials: config.Credentials,
|
||||
pullTomb: tomb.Tomb{},
|
||||
pushTomb: tomb.Tomb{},
|
||||
metricsTomb: tomb.Tomb{},
|
||||
scenarioList: make([]string, 0),
|
||||
consoleConfig: consoleConfig,
|
||||
}
|
||||
|
||||
ret.pullInterval, err = time.ParseDuration(PullInterval)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret.pushInterval, err = time.ParseDuration(PushInterval)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret.metricsInterval, err = time.ParseDuration(MetricsInterval)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
alertToPush: make(chan []*models.Alert),
|
||||
dbClient: dbClient,
|
||||
mu: sync.Mutex{},
|
||||
startup: true,
|
||||
credentials: config.Credentials,
|
||||
pullTomb: tomb.Tomb{},
|
||||
pushTomb: tomb.Tomb{},
|
||||
metricsTomb: tomb.Tomb{},
|
||||
scenarioList: make([]string, 0),
|
||||
consoleConfig: consoleConfig,
|
||||
pullInterval: PullInterval,
|
||||
pushInterval: PushInterval,
|
||||
metricsInterval: MetricsInterval,
|
||||
}
|
||||
|
||||
password := strfmt.Password(config.Credentials.Password)
|
||||
|
@ -140,6 +125,7 @@ func NewAPIC(config *csconfig.OnlineApiClientCfg, dbClient *database.Client, con
|
|||
return ret, err
|
||||
}
|
||||
|
||||
// keep track of all alerts in cache and push it to CAPI every PushInterval.
|
||||
func (a *apic) Push() error {
|
||||
defer types.CatchPanic("lapi/pushToAPIC")
|
||||
|
||||
|
@ -170,39 +156,9 @@ func (a *apic) Push() error {
|
|||
case alerts := <-a.alertToPush:
|
||||
var signals []*models.AddSignalsRequestItem
|
||||
for _, alert := range alerts {
|
||||
if *alert.Simulated {
|
||||
log.Debugf("simulation enabled for alert (id:%d), will not be sent to CAPI", alert.ID)
|
||||
continue
|
||||
if ok := shouldShareAlert(alert, a.consoleConfig); ok {
|
||||
signals = append(signals, alertToSignal(alert, getScenarioTrustOfAlert(alert)))
|
||||
}
|
||||
scenarioTrust := "certified"
|
||||
if alert.ScenarioHash == nil || *alert.ScenarioHash == "" {
|
||||
scenarioTrust = "custom"
|
||||
} else if alert.ScenarioVersion == nil || *alert.ScenarioVersion == "" || *alert.ScenarioVersion == "?" {
|
||||
scenarioTrust = "tainted"
|
||||
}
|
||||
if len(alert.Decisions) > 0 {
|
||||
if *alert.Decisions[0].Origin == "cscli" {
|
||||
scenarioTrust = "manual"
|
||||
}
|
||||
}
|
||||
switch scenarioTrust {
|
||||
case "manual":
|
||||
if !*a.consoleConfig.ShareManualDecisions {
|
||||
log.Debugf("manual decision generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
continue
|
||||
}
|
||||
case "tainted":
|
||||
if !*a.consoleConfig.ShareTaintedScenarios {
|
||||
log.Debugf("tainted scenario generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
continue
|
||||
}
|
||||
case "custom":
|
||||
if !*a.consoleConfig.ShareCustomScenarios {
|
||||
log.Debugf("custom scenario generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
continue
|
||||
}
|
||||
}
|
||||
signals = append(signals, AlertToSignal(alert, scenarioTrust))
|
||||
}
|
||||
a.mu.Lock()
|
||||
cache = append(cache, signals...)
|
||||
|
@ -211,6 +167,46 @@ func (a *apic) Push() error {
|
|||
}
|
||||
}
|
||||
|
||||
func getScenarioTrustOfAlert(alert *models.Alert) string {
|
||||
scenarioTrust := "certified"
|
||||
if alert.ScenarioHash == nil || *alert.ScenarioHash == "" {
|
||||
scenarioTrust = "custom"
|
||||
} else if alert.ScenarioVersion == nil || *alert.ScenarioVersion == "" || *alert.ScenarioVersion == "?" {
|
||||
scenarioTrust = "tainted"
|
||||
}
|
||||
if len(alert.Decisions) > 0 {
|
||||
if *alert.Decisions[0].Origin == "cscli" {
|
||||
scenarioTrust = "manual"
|
||||
}
|
||||
}
|
||||
return scenarioTrust
|
||||
}
|
||||
|
||||
func shouldShareAlert(alert *models.Alert, consoleConfig *csconfig.ConsoleConfig) bool {
|
||||
if *alert.Simulated {
|
||||
log.Debugf("simulation enabled for alert (id:%d), will not be sent to CAPI", alert.ID)
|
||||
return false
|
||||
}
|
||||
switch scenarioTrust := getScenarioTrustOfAlert(alert); scenarioTrust {
|
||||
case "manual":
|
||||
if !*consoleConfig.ShareManualDecisions {
|
||||
log.Debugf("manual decision generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
return false
|
||||
}
|
||||
case "tainted":
|
||||
if !*consoleConfig.ShareTaintedScenarios {
|
||||
log.Debugf("tainted scenario generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
return false
|
||||
}
|
||||
case "custom":
|
||||
if !*consoleConfig.ShareCustomScenarios {
|
||||
log.Debugf("custom scenario generated an alert, doesn't send it to CAPI because options is disabled")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *apic) Send(cacheOrig *models.AddSignalsRequest) {
|
||||
/*we do have a problem with this :
|
||||
The apic.Push background routine reads from alertToPush chan.
|
||||
|
@ -256,54 +252,26 @@ func (a *apic) Send(cacheOrig *models.AddSignalsRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
var SCOPE_CAPI string = "CAPI"
|
||||
var SCOPE_CAPI_ALIAS string = "crowdsecurity/community-blocklist" //we don't use "CAPI" directly, to make it less confusing for the user
|
||||
var SCOPE_LISTS string = "lists"
|
||||
|
||||
func (a *apic) PullTop() error {
|
||||
var err error
|
||||
|
||||
func (a *apic) CAPIPullIsOld() (bool, error) {
|
||||
/*only pull community blocklist if it's older than 1h30 */
|
||||
alerts := a.dbClient.Ent.Alert.Query()
|
||||
alerts = alerts.Where(alert.HasDecisionsWith(decision.OriginEQ(database.CapiMachineID)))
|
||||
alerts = alerts.Where(alert.CreatedAtGTE(time.Now().UTC().Add(-time.Duration(1*time.Hour + 30*time.Minute))))
|
||||
count, err := alerts.Count(a.dbClient.CTX)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "while looking for CAPI alert")
|
||||
return false, errors.Wrap(err, "while looking for CAPI alert")
|
||||
}
|
||||
if count > 0 {
|
||||
log.Printf("last CAPI pull is newer than 1h30, skip.")
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
data, _, err := a.apiClient.Decisions.GetStream(context.Background(), apiclient.DecisionsStreamOpts{Startup: a.startup})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get stream")
|
||||
}
|
||||
if a.startup {
|
||||
a.startup = false
|
||||
}
|
||||
/*to count additions/deletions accross lists*/
|
||||
var add_counters map[string]map[string]int
|
||||
var delete_counters map[string]map[string]int
|
||||
return true, nil
|
||||
}
|
||||
|
||||
add_counters = make(map[string]map[string]int)
|
||||
add_counters[SCOPE_CAPI] = make(map[string]int)
|
||||
add_counters[SCOPE_LISTS] = make(map[string]int)
|
||||
delete_counters = make(map[string]map[string]int)
|
||||
delete_counters[SCOPE_CAPI] = make(map[string]int)
|
||||
delete_counters[SCOPE_LISTS] = make(map[string]int)
|
||||
func (a *apic) HandleDeletedDecisions(deletedDecisions []*models.Decision, delete_counters map[string]map[string]int) (int, error) {
|
||||
var filter map[string][]string
|
||||
var nbDeleted int
|
||||
// process deleted decisions
|
||||
for _, decision := range data.Deleted {
|
||||
//count individual deletions
|
||||
if *decision.Origin == SCOPE_CAPI {
|
||||
delete_counters[SCOPE_CAPI][*decision.Scenario]++
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
delete_counters[SCOPE_LISTS][*decision.Scenario]++
|
||||
} else {
|
||||
log.Warningf("Unknown origin %s", *decision.Origin)
|
||||
}
|
||||
for _, decision := range deletedDecisions {
|
||||
if strings.ToLower(*decision.Scope) == "ip" {
|
||||
filter = make(map[string][]string, 1)
|
||||
filter["value"] = []string{*decision.Value}
|
||||
|
@ -311,36 +279,30 @@ func (a *apic) PullTop() error {
|
|||
filter = make(map[string][]string, 3)
|
||||
filter["value"] = []string{*decision.Value}
|
||||
filter["type"] = []string{*decision.Type}
|
||||
filter["value"] = []string{*decision.Scope}
|
||||
filter["scopes"] = []string{*decision.Scope}
|
||||
}
|
||||
filter["origin"] = []string{*decision.Origin}
|
||||
|
||||
dbCliRet, err := a.dbClient.SoftDeleteDecisionsWithFilter(filter)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deleting decisions error")
|
||||
return 0, errors.Wrap(err, "deleting decisions error")
|
||||
}
|
||||
dbCliDel, err := strconv.Atoi(dbCliRet)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "converting db ret %d", dbCliDel)
|
||||
return 0, errors.Wrapf(err, "converting db ret %d", dbCliDel)
|
||||
}
|
||||
updateCounterForDecision(delete_counters, decision, dbCliDel)
|
||||
nbDeleted += dbCliDel
|
||||
}
|
||||
log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)
|
||||
return nbDeleted, nil
|
||||
|
||||
if len(data.New) == 0 {
|
||||
log.Warnf("capi/community-blocklist : received 0 new entries, CAPI failure ?")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//we receive only one list of decisions, that we need to break-up :
|
||||
// one alert for "community blocklist"
|
||||
// one alert per list we're subscribed to
|
||||
var alertsFromCapi []*models.Alert
|
||||
alertsFromCapi = make([]*models.Alert, 0)
|
||||
|
||||
//iterate over all new decisions, and simply create corresponding alerts
|
||||
for _, decision := range data.New {
|
||||
func createAlertsForDecisions(decisions []*models.Decision) []*models.Alert {
|
||||
newAlerts := make([]*models.Alert, 0)
|
||||
for _, decision := range decisions {
|
||||
found := false
|
||||
for _, sub := range alertsFromCapi {
|
||||
for _, sub := range newAlerts {
|
||||
if sub.Source.Scope == nil {
|
||||
log.Warningf("nil scope in %+v", sub)
|
||||
continue
|
||||
|
@ -366,42 +328,44 @@ func (a *apic) PullTop() error {
|
|||
}
|
||||
if !found {
|
||||
log.Debugf("Create entry for origin:%s scenario:%s", *decision.Origin, *decision.Scenario)
|
||||
newAlert := models.Alert{}
|
||||
newAlert.Message = types.StrPtr("")
|
||||
newAlert.Source = &models.Source{}
|
||||
if *decision.Origin == SCOPE_CAPI { //to make things more user friendly, we replace CAPI with community-blocklist
|
||||
newAlert.Source.Scope = types.StrPtr(SCOPE_CAPI)
|
||||
newAlert.Scenario = types.StrPtr(SCOPE_CAPI)
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
newAlert.Source.Scope = types.StrPtr(SCOPE_LISTS)
|
||||
newAlert.Scenario = types.StrPtr(*decision.Scenario)
|
||||
} else {
|
||||
log.Warningf("unknown origin %s", *decision.Origin)
|
||||
}
|
||||
newAlert.Source.Value = types.StrPtr("")
|
||||
newAlert.StartAt = types.StrPtr(time.Now().UTC().Format(time.RFC3339))
|
||||
newAlert.StopAt = types.StrPtr(time.Now().UTC().Format(time.RFC3339))
|
||||
newAlert.Capacity = types.Int32Ptr(0)
|
||||
newAlert.Simulated = types.BoolPtr(false)
|
||||
newAlert.EventsCount = types.Int32Ptr(int32(len(data.New)))
|
||||
newAlert.Leakspeed = types.StrPtr("")
|
||||
newAlert.ScenarioHash = types.StrPtr("")
|
||||
newAlert.ScenarioVersion = types.StrPtr("")
|
||||
newAlert.MachineID = database.CapiMachineID
|
||||
alertsFromCapi = append(alertsFromCapi, &newAlert)
|
||||
newAlerts = append(newAlerts, createAlertForDecision(decision))
|
||||
}
|
||||
}
|
||||
return newAlerts
|
||||
}
|
||||
|
||||
//iterate a second time and fill the alerts with the new decisions
|
||||
for _, decision := range data.New {
|
||||
func createAlertForDecision(decision *models.Decision) *models.Alert {
|
||||
newAlert := &models.Alert{}
|
||||
newAlert.Source = &models.Source{}
|
||||
newAlert.Source.Scope = types.StrPtr("")
|
||||
if *decision.Origin == SCOPE_CAPI { //to make things more user friendly, we replace CAPI with community-blocklist
|
||||
newAlert.Scenario = types.StrPtr(SCOPE_CAPI)
|
||||
newAlert.Source.Scope = types.StrPtr(SCOPE_CAPI)
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
newAlert.Scenario = types.StrPtr(*decision.Scenario)
|
||||
newAlert.Source.Scope = types.StrPtr(SCOPE_LISTS)
|
||||
} else {
|
||||
log.Warningf("unknown origin %s", *decision.Origin)
|
||||
}
|
||||
newAlert.Message = types.StrPtr("")
|
||||
newAlert.Source.Value = types.StrPtr("")
|
||||
newAlert.StartAt = types.StrPtr(time.Now().UTC().Format(time.RFC3339))
|
||||
newAlert.StopAt = types.StrPtr(time.Now().UTC().Format(time.RFC3339))
|
||||
newAlert.Capacity = types.Int32Ptr(0)
|
||||
newAlert.Simulated = types.BoolPtr(false)
|
||||
newAlert.EventsCount = types.Int32Ptr(0)
|
||||
newAlert.Leakspeed = types.StrPtr("")
|
||||
newAlert.ScenarioHash = types.StrPtr("")
|
||||
newAlert.ScenarioVersion = types.StrPtr("")
|
||||
newAlert.MachineID = database.CapiMachineID
|
||||
return newAlert
|
||||
}
|
||||
|
||||
// This function takes in list of parent alerts and decisions and then pairs them up.
|
||||
func fillAlertsWithDecisions(alerts []*models.Alert, decisions []*models.Decision, add_counters map[string]map[string]int) []*models.Alert {
|
||||
for _, decision := range decisions {
|
||||
//count and create separate alerts for each list
|
||||
if *decision.Origin == SCOPE_CAPI {
|
||||
add_counters[SCOPE_CAPI]["all"]++
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
add_counters[SCOPE_LISTS][*decision.Scenario]++
|
||||
} else {
|
||||
log.Warningf("Unknown origin %s", *decision.Origin)
|
||||
}
|
||||
updateCounterForDecision(add_counters, decision, 1)
|
||||
|
||||
/*CAPI might send lower case scopes, unify it.*/
|
||||
switch strings.ToLower(*decision.Scope) {
|
||||
|
@ -412,16 +376,16 @@ func (a *apic) PullTop() error {
|
|||
}
|
||||
found := false
|
||||
//add the individual decisions to the right list
|
||||
for idx, alert := range alertsFromCapi {
|
||||
for idx, alert := range alerts {
|
||||
if *decision.Origin == SCOPE_CAPI {
|
||||
if *alert.Source.Scope == SCOPE_CAPI {
|
||||
alertsFromCapi[idx].Decisions = append(alertsFromCapi[idx].Decisions, decision)
|
||||
alerts[idx].Decisions = append(alerts[idx].Decisions, decision)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
if *alert.Source.Scope == SCOPE_LISTS && *alert.Scenario == *decision.Scenario {
|
||||
alertsFromCapi[idx].Decisions = append(alertsFromCapi[idx].Decisions, decision)
|
||||
alerts[idx].Decisions = append(alerts[idx].Decisions, decision)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
@ -433,18 +397,49 @@ func (a *apic) PullTop() error {
|
|||
log.Warningf("Orphaned decision for %s - %s", *decision.Origin, *decision.Scenario)
|
||||
}
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
|
||||
//we receive only one list of decisions, that we need to break-up :
|
||||
// one alert for "community blocklist"
|
||||
// one alert per list we're subscribed to
|
||||
func (a *apic) PullTop() error {
|
||||
var err error
|
||||
|
||||
if lastPullIsOld, err := a.CAPIPullIsOld(); err != nil {
|
||||
return err
|
||||
} else if !lastPullIsOld {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, _, err := a.apiClient.Decisions.GetStream(context.Background(), apiclient.DecisionsStreamOpts{Startup: a.startup})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get stream")
|
||||
}
|
||||
a.startup = false
|
||||
/*to count additions/deletions accross lists*/
|
||||
|
||||
add_counters, delete_counters := makeAddAndDeleteCounters()
|
||||
// process deleted decisions
|
||||
if nbDeleted, err := a.HandleDeletedDecisions(data.Deleted, delete_counters); err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)
|
||||
}
|
||||
|
||||
if len(data.New) == 0 {
|
||||
log.Warnf("capi/community-blocklist : received 0 new entries, CAPI failure ?")
|
||||
return nil
|
||||
}
|
||||
|
||||
//we receive only one list of decisions, that we need to break-up :
|
||||
// one alert for "community blocklist"
|
||||
// one alert per list we're subscribed to
|
||||
alertsFromCapi := createAlertsForDecisions(data.New)
|
||||
alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, data.New, add_counters)
|
||||
|
||||
for idx, alert := range alertsFromCapi {
|
||||
formatted_update := ""
|
||||
|
||||
if *alertsFromCapi[idx].Source.Scope == SCOPE_CAPI {
|
||||
*alertsFromCapi[idx].Source.Scope = SCOPE_CAPI_ALIAS
|
||||
formatted_update = fmt.Sprintf("update : +%d/-%d IPs", add_counters[SCOPE_CAPI]["all"], delete_counters[SCOPE_CAPI]["all"])
|
||||
} else if *alertsFromCapi[idx].Source.Scope == SCOPE_LISTS {
|
||||
*alertsFromCapi[idx].Source.Scope = fmt.Sprintf("%s:%s", SCOPE_LISTS, *alertsFromCapi[idx].Scenario)
|
||||
formatted_update = fmt.Sprintf("update : +%d/-%d IPs", add_counters[SCOPE_LISTS][*alert.Scenario], delete_counters[SCOPE_LISTS][*alert.Scenario])
|
||||
}
|
||||
alertsFromCapi[idx].Scenario = types.StrPtr(formatted_update)
|
||||
alertsFromCapi[idx] = setAlertScenario(add_counters, delete_counters, alert)
|
||||
log.Debugf("%s has %d decisions", *alertsFromCapi[idx].Source.Scope, len(alertsFromCapi[idx].Decisions))
|
||||
alertID, inserted, deleted, err := a.dbClient.UpdateCommunityBlocklist(alertsFromCapi[idx])
|
||||
if err != nil {
|
||||
|
@ -455,14 +450,27 @@ func (a *apic) PullTop() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setAlertScenario(add_counters map[string]map[string]int, delete_counters map[string]map[string]int, alert *models.Alert) *models.Alert {
|
||||
if *alert.Source.Scope == SCOPE_CAPI {
|
||||
*alert.Source.Scope = SCOPE_CAPI_ALIAS
|
||||
alert.Scenario = types.StrPtr(fmt.Sprintf("update : +%d/-%d IPs", add_counters[SCOPE_CAPI]["all"], delete_counters[SCOPE_CAPI]["all"]))
|
||||
} else if *alert.Source.Scope == SCOPE_LISTS {
|
||||
*alert.Source.Scope = fmt.Sprintf("%s:%s", SCOPE_LISTS, *alert.Scenario)
|
||||
alert.Scenario = types.StrPtr(fmt.Sprintf("update : +%d/-%d IPs", add_counters[SCOPE_LISTS][*alert.Scenario], delete_counters[SCOPE_LISTS][*alert.Scenario]))
|
||||
}
|
||||
return alert
|
||||
}
|
||||
|
||||
func (a *apic) Pull() error {
|
||||
defer types.CatchPanic("lapi/pullFromAPIC")
|
||||
log.Infof("start crowdsec api pull (interval: %s)", PullInterval)
|
||||
var err error
|
||||
|
||||
scenario := a.scenarioList
|
||||
toldOnce := false
|
||||
for {
|
||||
scenario, err := a.FetchScenariosListFromDB()
|
||||
if err != nil {
|
||||
log.Errorf("unable to fetch scenarios from db: %s", err)
|
||||
}
|
||||
if len(scenario) > 0 {
|
||||
break
|
||||
}
|
||||
|
@ -471,10 +479,6 @@ func (a *apic) Pull() error {
|
|||
toldOnce = true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
scenario, err = a.FetchScenariosListFromDB()
|
||||
if err != nil {
|
||||
log.Errorf("unable to fetch scenarios from db: %s", err)
|
||||
}
|
||||
}
|
||||
if err := a.PullTop(); err != nil {
|
||||
log.Errorf("capi pull top: %s", err)
|
||||
|
@ -496,9 +500,8 @@ func (a *apic) Pull() error {
|
|||
}
|
||||
|
||||
func (a *apic) GetMetrics() (*models.Metrics, error) {
|
||||
version := cwversion.VersionStr()
|
||||
metric := &models.Metrics{
|
||||
ApilVersion: &version,
|
||||
ApilVersion: types.StrPtr(cwversion.VersionStr()),
|
||||
Machines: make([]*models.MetricsAgentInfo, 0),
|
||||
Bouncers: make([]*models.MetricsBouncerInfo, 0),
|
||||
}
|
||||
|
@ -578,3 +581,25 @@ func (a *apic) Shutdown() {
|
|||
a.pullTomb.Kill(nil)
|
||||
a.metricsTomb.Kill(nil)
|
||||
}
|
||||
|
||||
func makeAddAndDeleteCounters() (map[string]map[string]int, map[string]map[string]int) {
|
||||
add_counters := make(map[string]map[string]int)
|
||||
add_counters[SCOPE_CAPI] = make(map[string]int)
|
||||
add_counters[SCOPE_LISTS] = make(map[string]int)
|
||||
|
||||
delete_counters := make(map[string]map[string]int)
|
||||
delete_counters[SCOPE_CAPI] = make(map[string]int)
|
||||
delete_counters[SCOPE_LISTS] = make(map[string]int)
|
||||
|
||||
return add_counters, delete_counters
|
||||
}
|
||||
|
||||
func updateCounterForDecision(counter map[string]map[string]int, decision *models.Decision, totalDecisions int) {
|
||||
if *decision.Origin == SCOPE_CAPI {
|
||||
counter[*decision.Origin]["all"] += totalDecisions
|
||||
return
|
||||
} else if *decision.Origin == SCOPE_LISTS {
|
||||
counter[*decision.Origin][*decision.Scenario] += totalDecisions
|
||||
}
|
||||
log.Warningf("Unknown origin %s", *decision.Origin)
|
||||
}
|
||||
|
|
956
pkg/apiserver/apic_test.go
Normal file
956
pkg/apiserver/apic_test.go
Normal file
|
@ -0,0 +1,956 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/machine"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
func getDBClient(t *testing.T) *database.Client {
|
||||
t.Helper()
|
||||
dbPath, err := os.CreateTemp("", "*sqlite")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dbClient, err := database.NewClient(&csconfig.DatabaseCfg{
|
||||
Type: "sqlite",
|
||||
DbName: "crowdsec",
|
||||
DbPath: dbPath.Name(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dbClient
|
||||
}
|
||||
|
||||
func getAPIC(t *testing.T) *apic {
|
||||
t.Helper()
|
||||
dbClient := getDBClient(t)
|
||||
return &apic{
|
||||
alertToPush: make(chan []*models.Alert),
|
||||
dbClient: dbClient,
|
||||
mu: sync.Mutex{},
|
||||
startup: true,
|
||||
pullTomb: tomb.Tomb{},
|
||||
pushTomb: tomb.Tomb{},
|
||||
metricsTomb: tomb.Tomb{},
|
||||
scenarioList: make([]string, 0),
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareManualDecisions: types.BoolPtr(false),
|
||||
ShareTaintedScenarios: types.BoolPtr(false),
|
||||
ShareCustomScenarios: types.BoolPtr(false),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func absDiff(a int, b int) (c int) {
|
||||
if c = a - b; c < 0 {
|
||||
return -1 * c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func assertTotalDecisionCount(t *testing.T, dbClient *database.Client, count int) {
|
||||
d := dbClient.Ent.Decision.Query().AllX(context.Background())
|
||||
assert.Len(t, d, count)
|
||||
}
|
||||
|
||||
func assertTotalValidDecisionCount(t *testing.T, dbClient *database.Client, count int) {
|
||||
d := dbClient.Ent.Decision.Query().Where(
|
||||
decision.UntilGT(time.Now()),
|
||||
).AllX(context.Background())
|
||||
assert.Len(t, d, count)
|
||||
}
|
||||
|
||||
func jsonMarshalX(v interface{}) []byte {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func assertTotalAlertCount(t *testing.T, dbClient *database.Client, count int) {
|
||||
d := dbClient.Ent.Alert.Query().AllX(context.Background())
|
||||
assert.Len(t, d, count)
|
||||
}
|
||||
|
||||
func TestAPICCAPIPullIsOld(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
isOld, err := api.CAPIPullIsOld()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.True(t, isOld)
|
||||
|
||||
decision := api.dbClient.Ent.Decision.Create().
|
||||
SetUntil(time.Now().Add(time.Hour)).
|
||||
SetScenario("crowdsec/test").
|
||||
SetType("IP").
|
||||
SetScope("Country").
|
||||
SetValue("Blah").
|
||||
SetOrigin(SCOPE_CAPI).
|
||||
SaveX(context.Background())
|
||||
|
||||
api.dbClient.Ent.Alert.Create().
|
||||
SetCreatedAt(time.Now()).
|
||||
SetScenario("crowdsec/test").
|
||||
AddDecisions(
|
||||
decision,
|
||||
).
|
||||
SaveX(context.Background())
|
||||
|
||||
isOld, err = api.CAPIPullIsOld()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.False(t, isOld)
|
||||
}
|
||||
|
||||
func TestAPICFetchScenariosListFromDB(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
machineIDsWithScenarios map[string]string
|
||||
expectedScenarios []string
|
||||
}{
|
||||
{
|
||||
name: "Simple one machine with two scenarios",
|
||||
machineIDsWithScenarios: map[string]string{
|
||||
"a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf",
|
||||
},
|
||||
expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf"},
|
||||
},
|
||||
{
|
||||
name: "Multi machine with custom+hub scenarios",
|
||||
machineIDsWithScenarios: map[string]string{
|
||||
"a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,my_scenario",
|
||||
"b": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,foo_scenario",
|
||||
},
|
||||
expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf", "my_scenario", "foo_scenario"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for machineID, scenarios := range tc.machineIDsWithScenarios {
|
||||
api.dbClient.Ent.Machine.Create().
|
||||
SetMachineId(machineID).
|
||||
SetPassword(testPassword.String()).
|
||||
SetIpAddress("1.2.3.4").
|
||||
SetScenarios(scenarios).
|
||||
ExecX(context.Background())
|
||||
}
|
||||
scenarios, err := api.FetchScenariosListFromDB()
|
||||
for machineID := range tc.machineIDsWithScenarios {
|
||||
api.dbClient.Ent.Machine.Delete().Where(machine.MachineIdEQ(machineID)).ExecX(context.Background())
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
sort.Strings(scenarios)
|
||||
sort.Strings(tc.expectedScenarios)
|
||||
assert.Equal(t, scenarios, tc.expectedScenarios)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAPIC(t *testing.T) {
|
||||
var testConfig *csconfig.OnlineApiClientCfg
|
||||
setConfig := func() {
|
||||
testConfig = &csconfig.OnlineApiClientCfg{
|
||||
Credentials: &csconfig.ApiCredentialsCfg{
|
||||
URL: "foobar",
|
||||
Login: "foo",
|
||||
Password: "bar",
|
||||
},
|
||||
}
|
||||
}
|
||||
type args struct {
|
||||
dbClient *database.Client
|
||||
consoleConfig *csconfig.ConsoleConfig
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
errorContains string
|
||||
action func()
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
action: func() {},
|
||||
args: args{
|
||||
dbClient: getDBClient(t),
|
||||
consoleConfig: LoadTestConfig().API.Server.ConsoleConfig,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error in parsing URL",
|
||||
action: func() { testConfig.Credentials.URL = "foobar http://" },
|
||||
args: args{
|
||||
dbClient: getDBClient(t),
|
||||
consoleConfig: LoadTestConfig().API.Server.ConsoleConfig,
|
||||
},
|
||||
wantErr: true,
|
||||
errorContains: "first path segment in URL cannot contain colon",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setConfig()
|
||||
tt.action()
|
||||
_, err := NewAPIC(testConfig, tt.args.dbClient, tt.args.consoleConfig)
|
||||
if tt.wantErr {
|
||||
assert.ErrorContains(t, err, tt.errorContains)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICHandleDeletedDecisions(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
_, deleteCounters := makeAddAndDeleteCounters()
|
||||
|
||||
decision1 := api.dbClient.Ent.Decision.Create().
|
||||
SetUntil(time.Now().Add(time.Hour)).
|
||||
SetScenario("crowdsec/test").
|
||||
SetType("ban").
|
||||
SetScope("IP").
|
||||
SetValue("1.2.3.4").
|
||||
SetOrigin(SCOPE_CAPI).
|
||||
SaveX(context.Background())
|
||||
|
||||
api.dbClient.Ent.Decision.Create().
|
||||
SetUntil(time.Now().Add(time.Hour)).
|
||||
SetScenario("crowdsec/test").
|
||||
SetType("ban").
|
||||
SetScope("IP").
|
||||
SetValue("1.2.3.4").
|
||||
SetOrigin(SCOPE_CAPI).
|
||||
SaveX(context.Background())
|
||||
|
||||
assertTotalDecisionCount(t, api.dbClient, 2)
|
||||
|
||||
nbDeleted, err := api.HandleDeletedDecisions([]*models.Decision{{
|
||||
Value: types.StrPtr("1.2.3.4"),
|
||||
Origin: &SCOPE_CAPI,
|
||||
Type: &decision1.Type,
|
||||
Scenario: types.StrPtr("crowdsec/test"),
|
||||
Scope: types.StrPtr("IP"),
|
||||
}}, deleteCounters)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nbDeleted, 2)
|
||||
assert.Equal(t, deleteCounters[SCOPE_CAPI]["all"], 2)
|
||||
}
|
||||
|
||||
func TestAPICGetMetrics(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
cleanUp := func() {
|
||||
api.dbClient.Ent.Bouncer.Delete().ExecX(context.Background())
|
||||
api.dbClient.Ent.Machine.Delete().ExecX(context.Background())
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
machineIDs []string
|
||||
bouncers []string
|
||||
expectedMetric *models.Metrics
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
machineIDs: []string{"a", "b", "c"},
|
||||
bouncers: []string{"1", "2", "3"},
|
||||
expectedMetric: &models.Metrics{
|
||||
ApilVersion: types.StrPtr(cwversion.VersionStr()),
|
||||
Bouncers: []*models.MetricsBouncerInfo{
|
||||
{
|
||||
CustomName: "1",
|
||||
LastPull: time.Time{}.String(),
|
||||
}, {
|
||||
CustomName: "2",
|
||||
LastPull: time.Time{}.String(),
|
||||
}, {
|
||||
CustomName: "3",
|
||||
LastPull: time.Time{}.String(),
|
||||
},
|
||||
},
|
||||
Machines: []*models.MetricsAgentInfo{
|
||||
{
|
||||
Name: "a",
|
||||
LastPush: time.Time{}.String(),
|
||||
LastUpdate: time.Time{}.String(),
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
LastPush: time.Time{}.String(),
|
||||
LastUpdate: time.Time{}.String(),
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
LastPush: time.Time{}.String(),
|
||||
LastUpdate: time.Time{}.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
cleanUp()
|
||||
for i, machineID := range testCase.machineIDs {
|
||||
api.dbClient.Ent.Machine.Create().
|
||||
SetMachineId(machineID).
|
||||
SetPassword(testPassword.String()).
|
||||
SetIpAddress(fmt.Sprintf("1.2.3.%d", i)).
|
||||
SetScenarios("crowdsecurity/test").
|
||||
SetLastPush(time.Time{}).
|
||||
SetUpdatedAt(time.Time{}).
|
||||
ExecX(context.Background())
|
||||
}
|
||||
|
||||
for i, bouncerName := range testCase.bouncers {
|
||||
api.dbClient.Ent.Bouncer.Create().
|
||||
SetIPAddress(fmt.Sprintf("1.2.3.%d", i)).
|
||||
SetName(bouncerName).
|
||||
SetAPIKey("foobar").
|
||||
SetRevoked(false).
|
||||
SetLastPull(time.Time{}).
|
||||
ExecX(context.Background())
|
||||
}
|
||||
|
||||
if foundMetrics, err := api.GetMetrics(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, foundMetrics.Bouncers, testCase.expectedMetric.Bouncers)
|
||||
assert.Equal(t, foundMetrics.Machines, testCase.expectedMetric.Machines)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAlertsForDecision(t *testing.T) {
|
||||
|
||||
httpBfDecisionList := &models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/http-bf"),
|
||||
}
|
||||
|
||||
sshBfDecisionList := &models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
}
|
||||
|
||||
httpBfDecisionCommunity := &models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/http-bf"),
|
||||
}
|
||||
|
||||
sshBfDecisionCommunity := &models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
}
|
||||
type args struct {
|
||||
decisions []*models.Decision
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*models.Alert
|
||||
}{
|
||||
{
|
||||
name: "2 decisions CAPI List Decisions should create 2 alerts",
|
||||
args: args{
|
||||
decisions: []*models.Decision{
|
||||
httpBfDecisionList,
|
||||
sshBfDecisionList,
|
||||
},
|
||||
},
|
||||
want: []*models.Alert{
|
||||
createAlertForDecision(httpBfDecisionList),
|
||||
createAlertForDecision(sshBfDecisionList),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "2 decisions CAPI List same scenario decisions should create 1 alert",
|
||||
args: args{
|
||||
decisions: []*models.Decision{
|
||||
httpBfDecisionList,
|
||||
httpBfDecisionList,
|
||||
},
|
||||
},
|
||||
want: []*models.Alert{
|
||||
createAlertForDecision(httpBfDecisionList),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "5 decisions from community list should create 1 alert",
|
||||
args: args{
|
||||
decisions: []*models.Decision{
|
||||
httpBfDecisionCommunity,
|
||||
httpBfDecisionCommunity,
|
||||
sshBfDecisionCommunity,
|
||||
sshBfDecisionCommunity,
|
||||
sshBfDecisionCommunity,
|
||||
},
|
||||
},
|
||||
want: []*models.Alert{
|
||||
createAlertForDecision(sshBfDecisionCommunity),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := createAlertsForDecisions(tt.args.decisions); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("createAlertsForDecisions() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillAlertsWithDecisions(t *testing.T) {
|
||||
httpBfDecisionCommunity := &models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/http-bf"),
|
||||
Scope: types.StrPtr("ip"),
|
||||
}
|
||||
|
||||
sshBfDecisionCommunity := &models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
Scope: types.StrPtr("ip"),
|
||||
}
|
||||
|
||||
httpBfDecisionList := &models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/http-bf"),
|
||||
Scope: types.StrPtr("ip"),
|
||||
}
|
||||
|
||||
sshBfDecisionList := &models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
Scope: types.StrPtr("ip"),
|
||||
}
|
||||
type args struct {
|
||||
alerts []*models.Alert
|
||||
decisions []*models.Decision
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*models.Alert
|
||||
}{
|
||||
{
|
||||
name: "1 CAPI alert should pair up with n CAPI decisions",
|
||||
args: args{
|
||||
alerts: []*models.Alert{createAlertForDecision(httpBfDecisionCommunity)},
|
||||
decisions: []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity},
|
||||
},
|
||||
want: []*models.Alert{
|
||||
func() *models.Alert {
|
||||
a := createAlertForDecision(httpBfDecisionCommunity)
|
||||
a.Decisions = []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity}
|
||||
return a
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List alert should pair up only with decisions having same scenario",
|
||||
args: args{
|
||||
alerts: []*models.Alert{createAlertForDecision(httpBfDecisionList), createAlertForDecision(sshBfDecisionList)},
|
||||
decisions: []*models.Decision{httpBfDecisionList, httpBfDecisionList, sshBfDecisionList, sshBfDecisionList},
|
||||
},
|
||||
want: []*models.Alert{
|
||||
func() *models.Alert {
|
||||
a := createAlertForDecision(httpBfDecisionList)
|
||||
a.Decisions = []*models.Decision{httpBfDecisionList, httpBfDecisionList}
|
||||
return a
|
||||
}(),
|
||||
func() *models.Alert {
|
||||
a := createAlertForDecision(sshBfDecisionList)
|
||||
a.Decisions = []*models.Decision{sshBfDecisionList, sshBfDecisionList}
|
||||
return a
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
add_counters, _ := makeAddAndDeleteCounters()
|
||||
if got := fillAlertsWithDecisions(tt.args.alerts, tt.args.decisions, add_counters); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("fillAlertsWithDecisions() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICPullTop(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
api.dbClient.Ent.Decision.Create().
|
||||
SetOrigin(SCOPE_LISTS).
|
||||
SetType("ban").
|
||||
SetValue("9.9.9.9").
|
||||
SetScope("Ip").
|
||||
SetScenario("crowdsecurity/ssh-bf").
|
||||
SetUntil(time.Now().Add(time.Hour)).
|
||||
ExecX(context.Background())
|
||||
assertTotalDecisionCount(t, api.dbClient, 1)
|
||||
assertTotalValidDecisionCount(t, api.dbClient, 1)
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
|
||||
200, jsonMarshalX(
|
||||
models.DecisionsStreamResponse{
|
||||
Deleted: models.GetDecisionsResponse{
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
Value: types.StrPtr("9.9.9.9"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
}, // Thie is already present in DB
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
Value: types.StrPtr("9.1.9.9"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
}, // This not present in DB.
|
||||
},
|
||||
New: models.GetDecisionsResponse{
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/test1"),
|
||||
Value: types.StrPtr("1.2.3.4"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
},
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/test2"),
|
||||
Value: types.StrPtr("1.2.3.5"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
}, // These two are from community list.
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/http-bf"),
|
||||
Value: types.StrPtr("1.2.3.6"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
},
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_LISTS,
|
||||
Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
|
||||
Value: types.StrPtr("1.2.3.7"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
}, // These two are from list subscription.
|
||||
},
|
||||
},
|
||||
),
|
||||
))
|
||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
apic, err := apiclient.NewDefaultClient(
|
||||
url,
|
||||
"/api",
|
||||
fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api.apiClient = apic
|
||||
err = api.PullTop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertTotalDecisionCount(t, api.dbClient, 5)
|
||||
assertTotalValidDecisionCount(t, api.dbClient, 4)
|
||||
assertTotalAlertCount(t, api.dbClient, 3) // 2 for list sub , 1 for community list.
|
||||
alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
|
||||
validDecisions := api.dbClient.Ent.Decision.Query().Where(
|
||||
decision.UntilGT(time.Now())).
|
||||
AllX(context.Background())
|
||||
|
||||
decisionScenarioFreq := make(map[string]int)
|
||||
alertScenario := make(map[string]int)
|
||||
|
||||
for _, alert := range alerts {
|
||||
alertScenario[alert.SourceScope]++
|
||||
}
|
||||
assert.Equal(t, len(alertScenario), 3)
|
||||
assert.Equal(t, alertScenario[SCOPE_CAPI_ALIAS], 1)
|
||||
assert.Equal(t, alertScenario["lists:crowdsecurity/ssh-bf"], 1)
|
||||
assert.Equal(t, alertScenario["lists:crowdsecurity/http-bf"], 1)
|
||||
|
||||
for _, decisions := range validDecisions {
|
||||
decisionScenarioFreq[decisions.Scenario]++
|
||||
}
|
||||
|
||||
assert.Equal(t, decisionScenarioFreq["crowdsecurity/http-bf"], 1)
|
||||
assert.Equal(t, decisionScenarioFreq["crowdsecurity/ssh-bf"], 1)
|
||||
assert.Equal(t, decisionScenarioFreq["crowdsecurity/test1"], 1)
|
||||
assert.Equal(t, decisionScenarioFreq["crowdsecurity/test2"], 1)
|
||||
}
|
||||
|
||||
func TestAPICPush(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
alerts []*models.Alert
|
||||
expectedCalls int
|
||||
}{
|
||||
{
|
||||
name: "simple single alert",
|
||||
alerts: []*models.Alert{
|
||||
{
|
||||
Scenario: types.StrPtr("crowdsec/test"),
|
||||
ScenarioHash: types.StrPtr("certified"),
|
||||
ScenarioVersion: types.StrPtr("v1.0"),
|
||||
Simulated: types.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "simulated alert is not pushed",
|
||||
alerts: []*models.Alert{
|
||||
{
|
||||
Scenario: types.StrPtr("crowdsec/test"),
|
||||
ScenarioHash: types.StrPtr("certified"),
|
||||
ScenarioVersion: types.StrPtr("v1.0"),
|
||||
Simulated: types.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
expectedCalls: 0,
|
||||
},
|
||||
{
|
||||
name: "1 request per 50 alerts",
|
||||
expectedCalls: 2,
|
||||
alerts: func() []*models.Alert {
|
||||
alerts := make([]*models.Alert, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
alerts[i] = &models.Alert{
|
||||
Scenario: types.StrPtr("crowdsec/test"),
|
||||
ScenarioHash: types.StrPtr("certified"),
|
||||
ScenarioVersion: types.StrPtr("v1.0"),
|
||||
Simulated: types.BoolPtr(false),
|
||||
}
|
||||
}
|
||||
return alerts
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
api.pushInterval = time.Millisecond
|
||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
apic, err := apiclient.NewDefaultClient(
|
||||
url,
|
||||
"/api",
|
||||
fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api.apiClient = apic
|
||||
httpmock.RegisterResponder("POST", "http://api.crowdsec.net/api/signals", httpmock.NewBytesResponder(200, []byte{}))
|
||||
go func() {
|
||||
api.alertToPush <- testCase.alerts
|
||||
time.Sleep(time.Second)
|
||||
api.Shutdown()
|
||||
}()
|
||||
if err := api.Push(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, httpmock.GetTotalCallCount(), testCase.expectedCalls)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICSendMetrics(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
duration time.Duration
|
||||
expectedCalls int
|
||||
setUp func()
|
||||
metricsInterval time.Duration
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
duration: time.Millisecond * 5,
|
||||
metricsInterval: time.Millisecond,
|
||||
expectedCalls: 5,
|
||||
setUp: func() {},
|
||||
},
|
||||
{
|
||||
name: "with some metrics",
|
||||
duration: time.Millisecond * 5,
|
||||
metricsInterval: time.Millisecond,
|
||||
expectedCalls: 5,
|
||||
setUp: func() {
|
||||
api.dbClient.Ent.Machine.Create().
|
||||
SetMachineId("1234").
|
||||
SetPassword(testPassword.String()).
|
||||
SetIpAddress("1.2.3.4").
|
||||
SetScenarios("crowdsecurity/test").
|
||||
SetLastPush(time.Time{}).
|
||||
SetUpdatedAt(time.Time{}).
|
||||
ExecX(context.Background())
|
||||
|
||||
api.dbClient.Ent.Bouncer.Create().
|
||||
SetIPAddress("1.2.3.6").
|
||||
SetName("someBouncer").
|
||||
SetAPIKey("foobar").
|
||||
SetRevoked(false).
|
||||
SetLastPull(time.Time{}).
|
||||
ExecX(context.Background())
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
api = getAPIC(t)
|
||||
api.pushInterval = time.Millisecond
|
||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
apic, err := apiclient.NewDefaultClient(
|
||||
url,
|
||||
"/api",
|
||||
fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api.apiClient = apic
|
||||
api.metricsInterval = testCase.metricsInterval
|
||||
httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, []byte{}))
|
||||
testCase.setUp()
|
||||
|
||||
go func() {
|
||||
if err := api.SendMetrics(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
time.Sleep(testCase.duration)
|
||||
assert.LessOrEqual(t, absDiff(testCase.expectedCalls, httpmock.GetTotalCallCount()), 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICPull(t *testing.T) {
|
||||
api := getAPIC(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
setUp func()
|
||||
expectedDecisionCount int
|
||||
logContains string
|
||||
}{
|
||||
{
|
||||
name: "test pull if no scenarios are present",
|
||||
setUp: func() {},
|
||||
logContains: "scenario list is empty, will not pull yet",
|
||||
},
|
||||
{
|
||||
name: "test pull",
|
||||
setUp: func() {
|
||||
api.dbClient.Ent.Machine.Create().
|
||||
SetMachineId("1.2.3.4").
|
||||
SetPassword(testPassword.String()).
|
||||
SetIpAddress("1.2.3.4").
|
||||
SetScenarios("crowdsecurity/ssh-bf").
|
||||
ExecX(context.Background())
|
||||
},
|
||||
expectedDecisionCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
api = getAPIC(t)
|
||||
api.pullInterval = time.Millisecond
|
||||
url, err := url.ParseRequestURI("http://api.crowdsec.net/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
apic, err := apiclient.NewDefaultClient(
|
||||
url,
|
||||
"/api",
|
||||
fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api.apiClient = apic
|
||||
httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, jsonMarshalX(
|
||||
models.DecisionsStreamResponse{
|
||||
New: models.GetDecisionsResponse{
|
||||
&models.Decision{
|
||||
Origin: &SCOPE_CAPI,
|
||||
Scenario: types.StrPtr("crowdsecurity/test2"),
|
||||
Value: types.StrPtr("1.2.3.5"),
|
||||
Scope: types.StrPtr("Ip"),
|
||||
Duration: types.StrPtr("24h"),
|
||||
Type: types.StrPtr("ban"),
|
||||
},
|
||||
},
|
||||
},
|
||||
)))
|
||||
testCase.setUp()
|
||||
var buf bytes.Buffer
|
||||
go func() {
|
||||
logrus.SetOutput(&buf)
|
||||
if err := api.Pull(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
logrus.SetOutput(os.Stderr)
|
||||
assert.Contains(t, buf.String(), testCase.logContains)
|
||||
assertTotalDecisionCount(t, api.dbClient, testCase.expectedDecisionCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldShareAlert(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
consoleConfig *csconfig.ConsoleConfig
|
||||
alert *models.Alert
|
||||
expectedRet bool
|
||||
expectedTrust string
|
||||
}{
|
||||
{
|
||||
name: "custom alert should be shared if config enables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareCustomScenarios: types.BoolPtr(true),
|
||||
},
|
||||
alert: &models.Alert{Simulated: types.BoolPtr(false)},
|
||||
expectedRet: true,
|
||||
expectedTrust: "custom",
|
||||
},
|
||||
{
|
||||
name: "custom alert should not be shared if config disables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareCustomScenarios: types.BoolPtr(false),
|
||||
},
|
||||
alert: &models.Alert{Simulated: types.BoolPtr(false)},
|
||||
expectedRet: false,
|
||||
expectedTrust: "custom",
|
||||
},
|
||||
{
|
||||
name: "manual alert should be shared if config enables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareManualDecisions: types.BoolPtr(true),
|
||||
},
|
||||
alert: &models.Alert{
|
||||
Simulated: types.BoolPtr(false),
|
||||
Decisions: []*models.Decision{{Origin: types.StrPtr("cscli")}},
|
||||
},
|
||||
expectedRet: true,
|
||||
expectedTrust: "manual",
|
||||
},
|
||||
{
|
||||
name: "manaul alert should not be shared if config disables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareManualDecisions: types.BoolPtr(false),
|
||||
},
|
||||
alert: &models.Alert{
|
||||
Simulated: types.BoolPtr(false),
|
||||
Decisions: []*models.Decision{{Origin: types.StrPtr("cscli")}},
|
||||
},
|
||||
expectedRet: false,
|
||||
expectedTrust: "manual",
|
||||
},
|
||||
{
|
||||
name: "manual alert should be shared if config enables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareTaintedScenarios: types.BoolPtr(true),
|
||||
},
|
||||
alert: &models.Alert{
|
||||
Simulated: types.BoolPtr(false),
|
||||
ScenarioHash: types.StrPtr("whateverHash"),
|
||||
},
|
||||
expectedRet: true,
|
||||
expectedTrust: "tainted",
|
||||
},
|
||||
{
|
||||
name: "manaul alert should not be shared if config disables it",
|
||||
consoleConfig: &csconfig.ConsoleConfig{
|
||||
ShareTaintedScenarios: types.BoolPtr(false),
|
||||
},
|
||||
alert: &models.Alert{
|
||||
Simulated: types.BoolPtr(false),
|
||||
ScenarioHash: types.StrPtr("whateverHash"),
|
||||
},
|
||||
expectedRet: false,
|
||||
expectedTrust: "tainted",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
ret := shouldShareAlert(testCase.alert, testCase.consoleConfig)
|
||||
assert.Equal(t, ret, testCase.expectedRet)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package apiserver
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
@ -16,6 +15,7 @@ import (
|
|||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||
|
@ -33,6 +33,7 @@ var MachineTest = models.WatcherAuthRequest{
|
|||
}
|
||||
|
||||
var UserAgent = fmt.Sprintf("crowdsec-test/%s", cwversion.Version)
|
||||
var emptyBody = strings.NewReader("")
|
||||
|
||||
func LoadTestConfig() csconfig.Config {
|
||||
config := csconfig.Config{}
|
||||
|
@ -177,6 +178,79 @@ func GetMachineIP(machineID string) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func GetAlertReaderFromFile(path string) *strings.Reader {
|
||||
|
||||
alertContentBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return strings.NewReader(string(alertContent))
|
||||
|
||||
}
|
||||
|
||||
func readDecisionsGetResp(resp *httptest.ResponseRecorder) ([]*models.Decision, int, error) {
|
||||
var response []*models.Decision
|
||||
if resp == nil {
|
||||
return nil, 0, errors.New("response is nil")
|
||||
}
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
return nil, resp.Code, err
|
||||
}
|
||||
return response, resp.Code, nil
|
||||
}
|
||||
|
||||
func readDecisionsErrorResp(resp *httptest.ResponseRecorder) (map[string]string, int, error) {
|
||||
var response map[string]string
|
||||
if resp == nil {
|
||||
return nil, 0, errors.New("response is nil")
|
||||
}
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
return nil, resp.Code, err
|
||||
}
|
||||
return response, resp.Code, nil
|
||||
}
|
||||
|
||||
func readDecisionsDeleteResp(resp *httptest.ResponseRecorder) (*models.DeleteDecisionResponse, int, error) {
|
||||
var response models.DeleteDecisionResponse
|
||||
if resp == nil {
|
||||
return nil, 0, errors.New("response is nil")
|
||||
}
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
return nil, resp.Code, err
|
||||
}
|
||||
return &response, resp.Code, nil
|
||||
}
|
||||
|
||||
func readDecisionsStreamResp(resp *httptest.ResponseRecorder) (map[string][]*models.Decision, int, error) {
|
||||
response := make(map[string][]*models.Decision)
|
||||
if resp == nil {
|
||||
return nil, 0, errors.New("response is nil")
|
||||
}
|
||||
err := json.Unmarshal(resp.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
return nil, resp.Code, err
|
||||
}
|
||||
return response, resp.Code, nil
|
||||
}
|
||||
|
||||
func CreateTestMachine(router *gin.Engine) (string, error) {
|
||||
b, err := json.Marshal(MachineTest)
|
||||
if err != nil {
|
||||
|
@ -306,7 +380,7 @@ func TestLoggingDebugToFileConfig(t *testing.T) {
|
|||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
//check file content
|
||||
data, err := ioutil.ReadFile(expectedFile)
|
||||
data, err := os.ReadFile(expectedFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file : %s", err)
|
||||
}
|
||||
|
@ -368,12 +442,11 @@ func TestLoggingErrorToFileConfig(t *testing.T) {
|
|||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
//check file content
|
||||
x, err := ioutil.ReadFile(expectedFile)
|
||||
x, err := os.ReadFile(expectedFile)
|
||||
if err == nil && len(x) > 0 {
|
||||
t.Fatalf("file should be empty, got '%s'", x)
|
||||
}
|
||||
|
||||
os.Remove("./crowdsec.log")
|
||||
os.Remove(expectedFile)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,571 +1,429 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeleteDecisionRange(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_minibulk.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
||||
|
||||
// delete by ip wrong
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?range=1.2.3.0/24", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?range=1.2.3.0/24", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||
|
||||
// delete by range
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
||||
|
||||
// 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(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?range=91.121.79.0/24", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||
}
|
||||
|
||||
func TestDeleteDecisionFilter(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_minibulk.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
||||
|
||||
// delete by ip wrong
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?ip=1.2.3.4", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?ip=1.2.3.4", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||
|
||||
// delete by ip good
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?ip=91.121.79.179", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?ip=91.121.79.179", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||
|
||||
// delete by scope/value
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?scopes=Ip&value=91.121.79.178", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions?scopes=Ip&value=91.121.79.178", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||
}
|
||||
|
||||
func TestGetDecisionFilters(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_minibulk.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
APIKey, err := CreateTestBouncer()
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err.Error())
|
||||
}
|
||||
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
||||
|
||||
// Get Decision
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
decisions, code, err := readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 2, len(decisions))
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||
assert.Equal(t, "91.121.79.179", *decisions[0].Value)
|
||||
assert.Equal(t, int64(1), decisions[0].ID)
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[1].Scenario)
|
||||
assert.Equal(t, "91.121.79.178", *decisions[1].Value)
|
||||
assert.Equal(t, int64(2), decisions[1].ID)
|
||||
|
||||
// Get Decision : type filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions?type=ban", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions?type=ban", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
decisions, code, err = readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 2, len(decisions))
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||
assert.Equal(t, "91.121.79.179", *decisions[0].Value)
|
||||
assert.Equal(t, int64(1), decisions[0].ID)
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[1].Scenario)
|
||||
assert.Equal(t, "91.121.79.178", *decisions[1].Value)
|
||||
assert.Equal(t, int64(2), decisions[1].ID)
|
||||
|
||||
// assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
// assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
|
||||
// Get Decision : scope/value
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions?scopes=Ip&value=91.121.79.179", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions?scopes=Ip&value=91.121.79.179", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
decisions, code, err = readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 1, len(decisions))
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||
assert.Equal(t, "91.121.79.179", *decisions[0].Value)
|
||||
assert.Equal(t, int64(1), decisions[0].ID)
|
||||
|
||||
// assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
// assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
|
||||
// Get Decision : ip filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions?ip=91.121.79.179", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions?ip=91.121.79.179", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
decisions, code, err = readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 1, len(decisions))
|
||||
assert.Equal(t, "crowdsecurity/ssh-bf", *decisions[0].Scenario)
|
||||
assert.Equal(t, "91.121.79.179", *decisions[0].Value)
|
||||
assert.Equal(t, int64(1), decisions[0].ID)
|
||||
|
||||
// assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
// assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
|
||||
// Get decision : by range
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions?range=91.121.79.0/24&contains=false", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions?range=91.121.79.0/24&contains=false", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
|
||||
assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
|
||||
decisions, code, err = readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 2, len(decisions))
|
||||
assert.Contains(t, []string{*decisions[0].Value, *decisions[1].Value}, "91.121.79.179")
|
||||
assert.Contains(t, []string{*decisions[0].Value, *decisions[1].Value}, "91.121.79.178")
|
||||
|
||||
}
|
||||
|
||||
func TestGetDecision(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
APIKey, err := CreateTestBouncer()
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err.Error())
|
||||
}
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
// Get Decision
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]")
|
||||
decisions, code, err := readDecisionsGetResp(w)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 3, len(decisions))
|
||||
/*decisions get doesn't perform deduplication*/
|
||||
assert.Equal(t, "crowdsecurity/test", *decisions[0].Scenario)
|
||||
assert.Equal(t, "127.0.0.1", *decisions[0].Value)
|
||||
assert.Equal(t, int64(1), decisions[0].ID)
|
||||
|
||||
assert.Equal(t, "crowdsecurity/test", *decisions[1].Scenario)
|
||||
assert.Equal(t, "127.0.0.1", *decisions[1].Value)
|
||||
assert.Equal(t, int64(2), decisions[1].ID)
|
||||
|
||||
assert.Equal(t, "crowdsecurity/test", *decisions[2].Scenario)
|
||||
assert.Equal(t, "127.0.0.1", *decisions[2].Value)
|
||||
assert.Equal(t, int64(3), decisions[2].ID)
|
||||
|
||||
// Get Decision with invalid filter. It should ignore this filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions?test=test", strings.NewReader(""))
|
||||
req.Header.Add("User-Agent", UserAgent)
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions?test=test", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]")
|
||||
|
||||
assert.Equal(t, 3, len(decisions))
|
||||
}
|
||||
|
||||
func TestDeleteDecisionByID(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
//Have one alerts
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err := readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
|
||||
// Delete alert with Invalid ID
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/test", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/test", emptyBody)
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"decision_id must be valid integer\"}", w.Body.String())
|
||||
err_resp, _, err := readDecisionsErrorResp(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, err_resp["message"], "decision_id must be valid integer")
|
||||
|
||||
// Delete alert with ID that not exist
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/100", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/100", emptyBody)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"decision with id '100' doesn't exist: unable to delete\"}", w.Body.String())
|
||||
err_resp, _, err = readDecisionsErrorResp(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, err_resp["message"], "decision with id '100' doesn't exist: unable to delete")
|
||||
|
||||
//Have one alerts
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
|
||||
// Delete alert with valid ID
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/1", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/1", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "{\"nbDeleted\":\"1\"}", w.Body.String())
|
||||
resp, _, err := readDecisionsDeleteResp(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resp.NbDeleted, "1")
|
||||
|
||||
//Have one alert (because we delete an alert that has dup targets)
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
}
|
||||
|
||||
func TestDeleteDecision(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
// Delete alert with Invalid filter
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?test=test", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w := lapi.RecordResponse("DELETE", "/v1/decisions?test=test", emptyBody)
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "{\"message\":\"'test' doesn't exist: invalid filter\"}", w.Body.String())
|
||||
|
||||
// Delete alert
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
err_resp, _, err := readDecisionsErrorResp(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, err_resp["message"], "'test' doesn't exist: invalid filter")
|
||||
|
||||
// Delete all alert
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "{\"nbDeleted\":\"3\"}", w.Body.String())
|
||||
|
||||
resp, _, err := readDecisionsDeleteResp(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resp.NbDeleted, "3")
|
||||
}
|
||||
|
||||
func TestStreamDecision(t *testing.T) {
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
func TestStreamStartDecisionDedup(t *testing.T) {
|
||||
//Ensure that at stream startup we only get the longest decision
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Create Valid Alert : 3 decisions for 127.0.0.1, longest has id=3
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
// Get Stream, we only get one decision (the longest one)
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err := readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(3))
|
||||
assert.Equal(t, *decisions["new"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["new"][0].Value, "127.0.0.1")
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
APIKey, err := CreateTestBouncer()
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err.Error())
|
||||
}
|
||||
|
||||
// Get Stream
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "{\"deleted\":null,\"new\":null}", w.Body.String())
|
||||
|
||||
// Get Stream just startup
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// the decision with id=3 is only returned because it's the longest decision
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]}")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":2")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":1")
|
||||
assert.Contains(t, w.Body.String(), "2h")
|
||||
|
||||
// id=3 decision is deleted, this won't affect `deleted`, because there are decisions
|
||||
// targetting same IP
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/3", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
// id=3 decision is deleted, this won't affect `deleted`, because there are decisions on the same ip
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/3", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
// the decision with id=2 is only returned because it's the longest decision
|
||||
assert.Contains(t, w.Body.String(), "\"id\":2,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]}")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":3")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":1")
|
||||
assert.Contains(t, w.Body.String(), "1h")
|
||||
assert.Contains(t, w.Body.String(), "\"deleted\":null")
|
||||
// Get Stream, we only get one decision (the longest one, id=2)
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(2))
|
||||
assert.Equal(t, *decisions["new"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["new"][0].Value, "127.0.0.1")
|
||||
|
||||
// We delete another decision, yet don't receive it in stream, since there's another decision on same IP
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/2", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/2", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "{\"deleted\":null,\"new\":null}", w.Body.String())
|
||||
|
||||
// Now all decisions for this IP are deleted, we should receive it in stream
|
||||
req, _ = http.NewRequest("DELETE", "/v1/decisions/1", strings.NewReader(""))
|
||||
AddAuthHeaders(req, loginResp)
|
||||
router.ServeHTTP(w, req)
|
||||
// And get the remaining decision (1)
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(1))
|
||||
assert.Equal(t, *decisions["new"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["new"][0].Value, "127.0.0.1")
|
||||
|
||||
// We delete the last decision, we receive the delete order
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/1", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
//and now we only get a deleted decision
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 1)
|
||||
assert.Equal(t, decisions["deleted"][0].ID, int64(1))
|
||||
assert.Equal(t, *decisions["deleted"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["deleted"][0].Value, "127.0.0.1")
|
||||
assert.Equal(t, len(decisions["new"]), 0)
|
||||
}
|
||||
|
||||
func TestStreamDecisionDedup(t *testing.T) {
|
||||
//Ensure that at stream startup we only get the longest decision
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert : 3 decisions for 127.0.0.1, longest has id=3
|
||||
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||
|
||||
// Get Stream, we only get one decision (the longest one)
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err := readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(3))
|
||||
assert.Equal(t, *decisions["new"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["new"][0].Value, "127.0.0.1")
|
||||
|
||||
// id=3 decision is deleted, this won't affect `deleted`, because there are decisions on the same ip
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/3", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream", emptyBody)
|
||||
assert.Equal(t, err, nil)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 0)
|
||||
|
||||
// We delete another decision, yet don't receive it in stream, since there's another decision on same IP
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/2", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 0)
|
||||
|
||||
// We delete the last decision, we receive the delete order
|
||||
w = lapi.RecordResponse("DELETE", "/v1/decisions/1", emptyBody)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, code, 200)
|
||||
assert.Equal(t, len(decisions["deleted"]), 1)
|
||||
assert.Equal(t, decisions["deleted"][0].ID, int64(1))
|
||||
assert.Equal(t, *decisions["deleted"][0].Origin, "test")
|
||||
assert.Equal(t, *decisions["deleted"][0].Value, "127.0.0.1")
|
||||
assert.Equal(t, len(decisions["new"]), 0)
|
||||
}
|
||||
|
||||
func TestStreamDecisionFilters(t *testing.T) {
|
||||
|
||||
router, loginResp, err := InitMachineTest()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
// Create Valid Alert
|
||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_stream_fixture.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
alerts := make([]*models.Alert, 0)
|
||||
if err := json.Unmarshal(alertContentBytes, &alerts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
lapi.InsertAlertFromFile("./tests/alert_stream_fixture.json")
|
||||
|
||||
for _, alert := range alerts {
|
||||
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
|
||||
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
w := lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true", emptyBody)
|
||||
decisions, code, err := readDecisionsStreamResp(w)
|
||||
|
||||
alertContent, err := json.Marshal(alerts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", "/v1/alerts", strings.NewReader(string(alertContent)))
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err.Error())
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
APIKey, err := CreateTestBouncer()
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err.Error())
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test1\",\"scenario\":\"crowdsecurity/http_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":2,\"origin\":\"test2\",\"scenario\":\"crowdsecurity/ssh_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test3\",\"scenario\":\"crowdsecurity/ddos\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 3)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(1))
|
||||
assert.Equal(t, *decisions["new"][0].Origin, "test1")
|
||||
assert.Equal(t, *decisions["new"][0].Value, "127.0.0.1")
|
||||
assert.Equal(t, *decisions["new"][0].Scenario, "crowdsecurity/http_bf")
|
||||
assert.Equal(t, decisions["new"][1].ID, int64(2))
|
||||
assert.Equal(t, *decisions["new"][1].Origin, "test2")
|
||||
assert.Equal(t, *decisions["new"][1].Value, "127.0.0.1")
|
||||
assert.Equal(t, *decisions["new"][1].Scenario, "crowdsecurity/ssh_bf")
|
||||
assert.Equal(t, decisions["new"][2].ID, int64(3))
|
||||
assert.Equal(t, *decisions["new"][2].Origin, "test3")
|
||||
assert.Equal(t, *decisions["new"][2].Value, "127.0.0.1")
|
||||
assert.Equal(t, *decisions["new"][2].Scenario, "crowdsecurity/ddos")
|
||||
|
||||
// test filter scenarios_not_containing
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true&scenarios_not_containing=http", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":1,\"origin\":\"test1\",\"scenario\":\"crowdsecurity/http_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":2,\"origin\":\"test2\",\"scenario\":\"crowdsecurity/ssh_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test3\",\"scenario\":\"crowdsecurity/ddos\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true&scenarios_not_containing=http", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 2)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(2))
|
||||
assert.Equal(t, decisions["new"][1].ID, int64(3))
|
||||
|
||||
// test filter scenarios_containing
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true&scenarios_containing=http", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test1\",\"scenario\":\"crowdsecurity/http_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":2,\"origin\":\"test2\",\"scenario\":\"crowdsecurity/ssh_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":3,\"origin\":\"test3\",\"scenario\":\"crowdsecurity/ddos\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true&scenarios_containing=http", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(1))
|
||||
|
||||
// test filters both by scenarios_not_containing and scenarios_containing
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true&scenarios_not_containing=ssh&scenarios_containing=ddos", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":1,\"origin\":\"test1\",\"scenario\":\"crowdsecurity/http_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":2,\"origin\":\"test2\",\"scenario\":\"crowdsecurity/ssh_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":3,\"origin\":\"test3\",\"scenario\":\"crowdsecurity/ddos\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true&scenarios_not_containing=ssh&scenarios_containing=ddos", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 1)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(3))
|
||||
|
||||
// test filter by origin
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/v1/decisions/stream?startup=true&origins=test1,test2", strings.NewReader(""))
|
||||
req.Header.Add("X-Api-Key", APIKey)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test1\",\"scenario\":\"crowdsecurity/http_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.Contains(t, w.Body.String(), "\"id\":2,\"origin\":\"test2\",\"scenario\":\"crowdsecurity/ssh_bf\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
assert.NotContains(t, w.Body.String(), "\"id\":3,\"origin\":\"test3\",\"scenario\":\"crowdsecurity/ddos\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"")
|
||||
w = lapi.RecordResponse("GET", "/v1/decisions/stream?startup=true&origins=test1,test2", emptyBody)
|
||||
decisions, code, err = readDecisionsStreamResp(w)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, len(decisions["deleted"]), 0)
|
||||
assert.Equal(t, len(decisions["new"]), 2)
|
||||
assert.Equal(t, decisions["new"][0].ID, int64(1))
|
||||
assert.Equal(t, decisions["new"][1].ID, int64(2))
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,548 @@
|
|||
[
|
||||
{"capacity":5,"decisions":null,"events":[{"meta":[{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"service","value":"ssh"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"target_user","value":"root"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"service","value":"ssh"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"}],"events_count":6,"labels":null,"leakspeed":"10s","message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202","remediation":true,"scenario":"crowdsecurity/ssh-bf","scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f","scenario_version":"0.1","simulated":true,"source":{"as_name":"OVH SAS","cn":"FR","ip":"91.121.79.179","latitude":50.646,"longitude":3.0758,"range":"91.121.72.0/21","scope":"Ip","value":"91.121.79.179"},"start_at":"2020-10-26T12:52:58.153861334+01:00","stop_at":"2020-10-26T12:52:58.200236582+01:00"},
|
||||
{"capacity":5,"decisions":null,"events":[{"meta":[{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"service","value":"ssh"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"target_user","value":"root"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"service","value":"ssh"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"}],"events_count":6,"labels":null,"leakspeed":"10s","message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202","remediation":true,"scenario":"crowdsecurity/ssh-bf","scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f","scenario_version":"0.1","simulated":false,"source":{"as_name":"OVH SAS","cn":"FR","ip":"91.121.79.178","latitude":50.646,"longitude":3.0758,"range":"91.121.72.0/21","scope":"Ip","value":"91.121.79.178"},"start_at":"2020-10-26T12:52:58.153861334+01:00","stop_at":"2020-10-26T12:52:58.200236582+01:00"}
|
||||
]
|
||||
{
|
||||
"capacity": 5,
|
||||
"decisions": null,
|
||||
"events": [
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
}
|
||||
],
|
||||
"events_count": 6,
|
||||
"labels": null,
|
||||
"leakspeed": "10s",
|
||||
"message": "Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202",
|
||||
"remediation": true,
|
||||
"scenario": "crowdsecurity/ssh-bf",
|
||||
"scenario_hash": "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||
"scenario_version": "0.1",
|
||||
"simulated": true,
|
||||
"source": {
|
||||
"as_name": "OVH SAS",
|
||||
"cn": "FR",
|
||||
"ip": "91.121.79.179",
|
||||
"latitude": 50.646,
|
||||
"longitude": 3.0758,
|
||||
"range": "91.121.72.0/21",
|
||||
"scope": "Ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
"start_at": "2020-10-26T12:52:58.153861334+01:00",
|
||||
"stop_at": "2020-10-26T12:52:58.200236582+01:00"
|
||||
},
|
||||
{
|
||||
"capacity": 5,
|
||||
"decisions": null,
|
||||
"events": [
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
}
|
||||
],
|
||||
"events_count": 6,
|
||||
"labels": null,
|
||||
"leakspeed": "10s",
|
||||
"message": "Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202",
|
||||
"remediation": true,
|
||||
"scenario": "crowdsecurity/ssh-bf",
|
||||
"scenario_hash": "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||
"scenario_version": "0.1",
|
||||
"simulated": false,
|
||||
"source": {
|
||||
"as_name": "OVH SAS",
|
||||
"cn": "FR",
|
||||
"ip": "91.121.79.178",
|
||||
"latitude": 50.646,
|
||||
"longitude": 3.0758,
|
||||
"range": "91.121.72.0/21",
|
||||
"scope": "Ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
"start_at": "2020-10-26T12:52:58.153861334+01:00",
|
||||
"stop_at": "2020-10-26T12:52:58.200236582+01:00"
|
||||
}
|
||||
]
|
|
@ -1,4 +1,548 @@
|
|||
[
|
||||
{"capacity":5,"decisions":null,"events":[{"meta":[{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"service","value":"ssh"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"target_user","value":"root"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"service","value":"ssh"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.179"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"}],"events_count":6,"labels":null,"leakspeed":"10s","message":"Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202","remediation":true,"scenario":"crowdsecurity/ssh-bf","scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f","scenario_version":"0.1","simulated":false,"source":{"as_name":"OVH SAS","cn":"FR","ip":"91.121.79.179","latitude":50.646,"longitude":3.0758,"range":"91.121.72.0/21","scope":"Ip","value":"91.121.79.179"},"start_at":"2020-10-26T12:52:58.153861334+01:00","stop_at":"2020-10-26T12:52:58.200236582+01:00"},
|
||||
{"capacity":5,"decisions":null,"events":[{"meta":[{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"service","value":"ssh"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"target_user","value":"root"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"service","value":"ssh"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"}],"timestamp":"2020-10-02T17:09:08Z"},{"meta":[{"key":"log_type","value":"ssh_failed-auth"},{"key":"source_ip","value":"91.121.79.178"},{"key":"ASNNumber","value":"16276"},{"key":"ASNOrg","value":"OVH SAS"},{"key":"SourceRange","value":"91.121.72.0/21"},{"key":"target_user","value":"root"},{"key":"service","value":"ssh"},{"key":"IsoCode","value":"FR"},{"key":"IsInEU","value":"true"}],"timestamp":"2020-10-02T17:09:08Z"}],"events_count":6,"labels":null,"leakspeed":"10s","message":"Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202","remediation":true,"scenario":"crowdsecurity/ssh-bf","scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f","scenario_version":"0.1","simulated":false,"source":{"as_name":"OVH SAS","cn":"FR","ip":"91.121.79.178","latitude":50.646,"longitude":3.0758,"range":"91.121.72.0/21","scope":"Ip","value":"91.121.79.178"},"start_at":"2020-10-26T12:52:58.153861334+01:00","stop_at":"2020-10-26T12:52:58.200236582+01:00"}
|
||||
]
|
||||
{
|
||||
"capacity": 5,
|
||||
"decisions": null,
|
||||
"events": [
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
}
|
||||
],
|
||||
"events_count": 6,
|
||||
"labels": null,
|
||||
"leakspeed": "10s",
|
||||
"message": "Ip 91.121.79.179 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202",
|
||||
"remediation": true,
|
||||
"scenario": "crowdsecurity/ssh-bf",
|
||||
"scenario_hash": "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||
"scenario_version": "0.1",
|
||||
"simulated": false,
|
||||
"source": {
|
||||
"as_name": "OVH SAS",
|
||||
"cn": "FR",
|
||||
"ip": "91.121.79.179",
|
||||
"latitude": 50.646,
|
||||
"longitude": 3.0758,
|
||||
"range": "91.121.72.0/21",
|
||||
"scope": "Ip",
|
||||
"value": "91.121.79.179"
|
||||
},
|
||||
"start_at": "2020-10-26T12:52:58.153861334+01:00",
|
||||
"stop_at": "2020-10-26T12:52:58.200236582+01:00"
|
||||
},
|
||||
{
|
||||
"capacity": 5,
|
||||
"decisions": null,
|
||||
"events": [
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
},
|
||||
{
|
||||
"meta": [
|
||||
{
|
||||
"key": "log_type",
|
||||
"value": "ssh_failed-auth"
|
||||
},
|
||||
{
|
||||
"key": "source_ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
{
|
||||
"key": "ASNNumber",
|
||||
"value": "16276"
|
||||
},
|
||||
{
|
||||
"key": "ASNOrg",
|
||||
"value": "OVH SAS"
|
||||
},
|
||||
{
|
||||
"key": "SourceRange",
|
||||
"value": "91.121.72.0/21"
|
||||
},
|
||||
{
|
||||
"key": "target_user",
|
||||
"value": "root"
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"value": "ssh"
|
||||
},
|
||||
{
|
||||
"key": "IsoCode",
|
||||
"value": "FR"
|
||||
},
|
||||
{
|
||||
"key": "IsInEU",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"timestamp": "2020-10-02T17:09:08Z"
|
||||
}
|
||||
],
|
||||
"events_count": 6,
|
||||
"labels": null,
|
||||
"leakspeed": "10s",
|
||||
"message": "Ip 91.121.79.178 performed crowdsecurity/ssh-bf (6 events over 46.375699ms) at 2020-10-26 12:52:58.200237122 +0100 CET m=+8.191478202",
|
||||
"remediation": true,
|
||||
"scenario": "crowdsecurity/ssh-bf",
|
||||
"scenario_hash": "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||
"scenario_version": "0.1",
|
||||
"simulated": false,
|
||||
"source": {
|
||||
"as_name": "OVH SAS",
|
||||
"cn": "FR",
|
||||
"ip": "91.121.79.178",
|
||||
"latitude": 50.646,
|
||||
"longitude": 3.0758,
|
||||
"range": "91.121.72.0/21",
|
||||
"scope": "Ip",
|
||||
"value": "91.121.79.178"
|
||||
},
|
||||
"start_at": "2020-10-26T12:52:58.153861334+01:00",
|
||||
"stop_at": "2020-10-26T12:52:58.200236582+01:00"
|
||||
}
|
||||
]
|
|
@ -74,4 +74,4 @@
|
|||
"start_at": "2020-10-09T10:00:01Z",
|
||||
"stop_at": "2020-10-09T10:00:05Z"
|
||||
}
|
||||
]
|
||||
]
|
|
@ -272,4 +272,4 @@
|
|||
"start_at": "2020-10-26T09:50:32.025353849+01:00",
|
||||
"stop_at": "2020-10-26T09:50:32.055534398+01:00"
|
||||
}
|
||||
]
|
||||
]
|
|
@ -30,7 +30,6 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var testMode bool = false
|
||||
var pluginMutex sync.Mutex
|
||||
|
||||
const (
|
||||
|
@ -255,6 +254,9 @@ func (pb *PluginBroker) loadNotificationPlugin(name string, binaryPath string) (
|
|||
}
|
||||
cmd := exec.Command(binaryPath)
|
||||
if pb.pluginProcConfig.User != "" || pb.pluginProcConfig.Group != "" {
|
||||
if !(pb.pluginProcConfig.User != "" && pb.pluginProcConfig.Group != "") {
|
||||
return nil, errors.New("while getting process attributes: both plugin user and group must be set")
|
||||
}
|
||||
cmd.SysProcAttr, err = getProcessAttr(pb.pluginProcConfig.User, pb.pluginProcConfig.Group)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "while getting process attributes")
|
||||
|
@ -360,9 +362,6 @@ func setRequiredFields(pluginCfg *PluginConfig) {
|
|||
}
|
||||
|
||||
func pluginIsValid(path string) error {
|
||||
if testMode {
|
||||
return nil
|
||||
}
|
||||
var details fs.FileInfo
|
||||
var err error
|
||||
|
||||
|
@ -387,7 +386,6 @@ func pluginIsValid(path string) error {
|
|||
|
||||
mode := details.Mode()
|
||||
perm := uint32(mode)
|
||||
|
||||
if (perm & 00002) != 0 {
|
||||
return fmt.Errorf("plugin at %s is world writable, world writable plugins are invalid", path)
|
||||
}
|
||||
|
|
|
@ -1,17 +1,36 @@
|
|||
package csplugin
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
var testPath string
|
||||
|
||||
func Test_getPluginNameAndTypeFromPath(t *testing.T) {
|
||||
func setPluginPermTo744() {
|
||||
setPluginPermTo("744")
|
||||
}
|
||||
|
||||
func setPluginPermTo722() {
|
||||
setPluginPermTo("722")
|
||||
}
|
||||
|
||||
func setPluginPermTo724() {
|
||||
setPluginPermTo("724")
|
||||
}
|
||||
func TestGetPluginNameAndTypeFromPath(t *testing.T) {
|
||||
setUp()
|
||||
defer tearDown()
|
||||
type args struct {
|
||||
|
@ -69,7 +88,7 @@ func Test_getPluginNameAndTypeFromPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_listFilesAtPath(t *testing.T) {
|
||||
func TestListFilesAtPath(t *testing.T) {
|
||||
setUp()
|
||||
defer tearDown()
|
||||
type args struct {
|
||||
|
@ -113,9 +132,176 @@ func Test_listFilesAtPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBrokerInit(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
action func()
|
||||
errContains string
|
||||
wantErr bool
|
||||
procCfg csconfig.PluginCfg
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
action: setPluginPermTo744,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "group writable binary",
|
||||
wantErr: true,
|
||||
errContains: "notification-dummy is world writable",
|
||||
action: setPluginPermTo722,
|
||||
},
|
||||
{
|
||||
name: "group writable binary",
|
||||
wantErr: true,
|
||||
errContains: "notification-dummy is group writable",
|
||||
action: setPluginPermTo724,
|
||||
},
|
||||
{
|
||||
name: "no plugin dir",
|
||||
wantErr: true,
|
||||
errContains: "no such file or directory",
|
||||
action: tearDown,
|
||||
},
|
||||
{
|
||||
name: "no plugin binary",
|
||||
wantErr: true,
|
||||
errContains: "binary for plugin dummy_default not found",
|
||||
action: func() {
|
||||
err := os.Remove(path.Join(testPath, "notification-dummy"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only specify user",
|
||||
wantErr: true,
|
||||
errContains: "both plugin user and group must be set",
|
||||
procCfg: csconfig.PluginCfg{
|
||||
User: "123445555551122toto",
|
||||
},
|
||||
action: setPluginPermTo744,
|
||||
},
|
||||
{
|
||||
name: "only specify group",
|
||||
wantErr: true,
|
||||
errContains: "both plugin user and group must be set",
|
||||
procCfg: csconfig.PluginCfg{
|
||||
Group: "123445555551122toto",
|
||||
},
|
||||
action: setPluginPermTo744,
|
||||
},
|
||||
{
|
||||
name: "Fails to run as root",
|
||||
wantErr: true,
|
||||
errContains: "operation not permitted",
|
||||
procCfg: csconfig.PluginCfg{
|
||||
User: "root",
|
||||
Group: "root",
|
||||
},
|
||||
action: setPluginPermTo744,
|
||||
},
|
||||
{
|
||||
name: "Invalid user and group",
|
||||
wantErr: true,
|
||||
errContains: "unknown user toto1234",
|
||||
procCfg: csconfig.PluginCfg{
|
||||
User: "toto1234",
|
||||
Group: "toto1234",
|
||||
},
|
||||
action: setPluginPermTo744,
|
||||
},
|
||||
{
|
||||
name: "Valid user and invalid group",
|
||||
wantErr: true,
|
||||
errContains: "unknown group toto1234",
|
||||
procCfg: csconfig.PluginCfg{
|
||||
User: "nobody",
|
||||
Group: "toto1234",
|
||||
},
|
||||
action: setPluginPermTo744,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer tearDown()
|
||||
buildDummyPlugin()
|
||||
if test.action != nil {
|
||||
test.action()
|
||||
}
|
||||
pb := PluginBroker{}
|
||||
profiles := csconfig.NewDefaultConfig().API.Server.Profiles
|
||||
profiles = append(profiles, &csconfig.ProfileCfg{
|
||||
Notifications: []string{"dummy_default"},
|
||||
})
|
||||
err := pb.Init(&test.procCfg, profiles, &csconfig.ConfigurationPaths{
|
||||
PluginDir: testPath,
|
||||
NotificationDir: "./tests/notifications",
|
||||
})
|
||||
defer pb.Kill()
|
||||
if test.wantErr {
|
||||
assert.ErrorContains(t, err, test.errContains)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrokerRun(t *testing.T) {
|
||||
buildDummyPlugin()
|
||||
setPluginPermTo744()
|
||||
defer tearDown()
|
||||
procCfg := csconfig.PluginCfg{}
|
||||
pb := PluginBroker{}
|
||||
profiles := csconfig.NewDefaultConfig().API.Server.Profiles
|
||||
profiles = append(profiles, &csconfig.ProfileCfg{
|
||||
Notifications: []string{"dummy_default"},
|
||||
})
|
||||
err := pb.Init(&procCfg, profiles, &csconfig.ConfigurationPaths{
|
||||
PluginDir: testPath,
|
||||
NotificationDir: "./tests/notifications",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
tomb := tomb.Tomb{}
|
||||
go pb.Run(&tomb)
|
||||
defer pb.Kill()
|
||||
|
||||
assert.NoFileExists(t, "./out")
|
||||
defer os.Remove("./out")
|
||||
|
||||
pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
|
||||
pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}}
|
||||
time.Sleep(time.Second * 4)
|
||||
|
||||
assert.FileExists(t, "./out")
|
||||
assert.Equal(t, types.GetLineCountForFile("./out"), 2)
|
||||
}
|
||||
|
||||
func buildDummyPlugin() {
|
||||
dir, err := os.MkdirTemp("./tests", "cs_plugin_test")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("go", "build", "-o", path.Join(dir, "notification-dummy"), "../../plugins/notifications/dummy/")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testPath = dir
|
||||
}
|
||||
|
||||
func setPluginPermTo(perm string) {
|
||||
if err := exec.Command("chmod", perm, path.Join(testPath, "notification-dummy")).Run(); err != nil {
|
||||
log.Fatal(errors.Wrapf(err, "chmod 744 %s", path.Join(testPath, "notification-dummy")))
|
||||
}
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
testMode = true
|
||||
dir, err := ioutil.TempDir("./", "cs_plugin_test")
|
||||
dir, err := os.MkdirTemp("./", "cs_plugin_test")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
22
pkg/csplugin/tests/notifications/dummy.yaml
Normal file
22
pkg/csplugin/tests/notifications/dummy.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
type: dummy # Don't change
|
||||
name: dummy_default # Must match the registered plugin in the profile
|
||||
|
||||
# One of "trace", "debug", "info", "warn", "error", "off"
|
||||
log_level: info
|
||||
|
||||
# group_wait: # Time to wait collecting alerts before relaying a message to this plugin, eg "30s"
|
||||
# group_threshold: # Amount of alerts that triggers a message before <group_wait> has expired, eg "10"
|
||||
# max_retry: # Number of attempts to relay messages to plugins in case of error
|
||||
# timeout: # Time to wait for response from the plugin before considering the attempt a failure, eg "10s"
|
||||
|
||||
#-------------------------
|
||||
# plugin-specific options
|
||||
|
||||
# The following template receives a list of models.Alert objects
|
||||
# The output goes in the logs and to a text file, if defined
|
||||
format: |
|
||||
{{.|toJson}}
|
||||
|
||||
#
|
||||
output_file: ./out # notifications will be appended here. optional
|
||||
|
107
pkg/csplugin/watcher_test.go
Normal file
107
pkg/csplugin/watcher_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package csplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"gopkg.in/tomb.v2"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func resetTestTomb(testTomb *tomb.Tomb) {
|
||||
testTomb.Kill(nil)
|
||||
if err := testTomb.Wait(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func resetWatcherAlertCounter(pw *PluginWatcher) {
|
||||
for k := range pw.AlertCountByPluginName {
|
||||
pw.AlertCountByPluginName[k] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func insertNAlertsToPlugin(pw *PluginWatcher, n int, pluginName string) {
|
||||
for i := 0; i < n; i++ {
|
||||
pw.Inserts <- pluginName
|
||||
}
|
||||
}
|
||||
|
||||
func listenChannelWithTimeout(ctx context.Context, channel chan string) error {
|
||||
select {
|
||||
case <-channel:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPluginWatcherInterval(t *testing.T) {
|
||||
pw := PluginWatcher{}
|
||||
alertsByPluginName := make(map[string][]*models.Alert)
|
||||
testTomb := tomb.Tomb{}
|
||||
configs := map[string]PluginConfig{
|
||||
"testPlugin": {
|
||||
GroupWait: time.Millisecond,
|
||||
},
|
||||
}
|
||||
pw.Init(configs, alertsByPluginName)
|
||||
pw.Start(&testTomb)
|
||||
|
||||
ct, cancel := context.WithTimeout(ctx, time.Microsecond)
|
||||
defer cancel()
|
||||
err := listenChannelWithTimeout(ct, pw.PluginEvents)
|
||||
assert.ErrorContains(t, err, "context deadline exceeded")
|
||||
|
||||
resetTestTomb(&testTomb)
|
||||
testTomb = tomb.Tomb{}
|
||||
pw.Start(&testTomb)
|
||||
|
||||
ct, cancel = context.WithTimeout(ctx, time.Millisecond*5)
|
||||
defer cancel()
|
||||
err = listenChannelWithTimeout(ct, pw.PluginEvents)
|
||||
assert.NilError(t, err)
|
||||
resetTestTomb(&testTomb)
|
||||
// This is to avoid the int complaining
|
||||
}
|
||||
|
||||
func TestPluginAlertCountWatcher(t *testing.T) {
|
||||
pw := PluginWatcher{}
|
||||
alertsByPluginName := make(map[string][]*models.Alert)
|
||||
configs := map[string]PluginConfig{
|
||||
"testPlugin": {
|
||||
GroupThreshold: 5,
|
||||
},
|
||||
}
|
||||
testTomb := tomb.Tomb{}
|
||||
pw.Init(configs, alertsByPluginName)
|
||||
pw.Start(&testTomb)
|
||||
|
||||
// Channel won't contain any events since threshold is not crossed.
|
||||
ct, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
err := listenChannelWithTimeout(ct, pw.PluginEvents)
|
||||
assert.ErrorContains(t, err, "context deadline exceeded")
|
||||
|
||||
// Channel won't contain any events since threshold is not crossed.
|
||||
resetWatcherAlertCounter(&pw)
|
||||
insertNAlertsToPlugin(&pw, 4, "testPlugin")
|
||||
ct, cancel = context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
err = listenChannelWithTimeout(ct, pw.PluginEvents)
|
||||
assert.ErrorContains(t, err, "context deadline exceeded")
|
||||
|
||||
// Channel will contain an event since threshold is crossed.
|
||||
resetWatcherAlertCounter(&pw)
|
||||
insertNAlertsToPlugin(&pw, 5, "testPlugin")
|
||||
ct, cancel = context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
err = listenChannelWithTimeout(ct, pw.PluginEvents)
|
||||
assert.NilError(t, err)
|
||||
resetTestTomb(&testTomb)
|
||||
}
|
|
@ -408,7 +408,7 @@ func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string,
|
|||
return strconv.Itoa(nbDeleted), nil
|
||||
}
|
||||
|
||||
// SoftDeleteDecisionsWithFilter udpate the expiration time to now() for the decisions matching the filter
|
||||
// SoftDeleteDecisionsWithFilter updates the expiration time to now() for the decisions matching the filter
|
||||
func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (string, error) {
|
||||
var err error
|
||||
var start_ip, start_sfx, end_ip, end_sfx int64
|
||||
|
@ -426,6 +426,8 @@ func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (stri
|
|||
}
|
||||
case "scopes":
|
||||
decisions = decisions.Where(decision.ScopeEQ(value[0]))
|
||||
case "origin":
|
||||
decisions = decisions.Where(decision.OriginEQ(value[0]))
|
||||
case "value":
|
||||
decisions = decisions.Where(decision.ValueEQ(value[0]))
|
||||
case "type":
|
||||
|
|
40
pkg/types/dataset_test.go
Normal file
40
pkg/types/dataset_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
)
|
||||
|
||||
func TestDownladFile(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
//OK
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://example.com/xx",
|
||||
httpmock.NewStringResponder(200, "example content oneoneone"),
|
||||
)
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://example.com/x",
|
||||
httpmock.NewStringResponder(404, "not found"),
|
||||
)
|
||||
err := downloadFile("https://example.com/xx", "./example.txt")
|
||||
assert.NoError(t, err)
|
||||
content, err := ioutil.ReadFile("./example.txt")
|
||||
assert.Equal(t, "example content oneoneone", string(content))
|
||||
assert.NoError(t, err)
|
||||
//bad uri
|
||||
err = downloadFile("https://zz.com", "./example.txt")
|
||||
assert.Error(t, err)
|
||||
//404
|
||||
err = downloadFile("https://example.com/x", "./example.txt")
|
||||
assert.Error(t, err)
|
||||
//bad target
|
||||
err = downloadFile("https://example.com/xx", "")
|
||||
assert.Error(t, err)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
@ -243,3 +244,17 @@ func InSlice(str string, slice []string) bool {
|
|||
func UtcNow() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
func GetLineCountForFile(filepath string) int {
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to open log file %s", filepath)
|
||||
}
|
||||
defer f.Close()
|
||||
lc := 0
|
||||
fs := bufio.NewScanner(f)
|
||||
for fs.Scan() {
|
||||
lc++
|
||||
}
|
||||
return lc
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ func (s *DummyPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
|
|||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Cannot open notification file: %s", err))
|
||||
}
|
||||
if _, err := f.WriteString(notification.Text); err != nil {
|
||||
if _, err := f.WriteString(notification.Text + "\n"); err != nil {
|
||||
f.Close()
|
||||
logger.Error(fmt.Sprintf("Cannot write notification to file: %s", err))
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func (s *DummyPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
|
|||
logger.Error(fmt.Sprintf("Cannot close notification file: %s", err))
|
||||
}
|
||||
}
|
||||
fmt.Print(notification.Text)
|
||||
fmt.Println(notification.Text)
|
||||
|
||||
return &protobufs.Empty{}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue