Add query param to filter decisions by scenarios and origin (#1294)
* Add query param to filter decisions by scenarios
This commit is contained in:
parent
bb30a3f966
commit
42a1bc0260
|
@ -3,7 +3,6 @@ package apiclient
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
qs "github.com/google/go-querystring/query"
|
qs "github.com/google/go-querystring/query"
|
||||||
|
@ -18,10 +17,24 @@ type DecisionsListOpts struct {
|
||||||
IPEquals *string `url:"ip,omitempty"`
|
IPEquals *string `url:"ip,omitempty"`
|
||||||
RangeEquals *string `url:"range,omitempty"`
|
RangeEquals *string `url:"range,omitempty"`
|
||||||
Contains *bool `url:"contains,omitempty"`
|
Contains *bool `url:"contains,omitempty"`
|
||||||
|
|
||||||
ListOpts
|
ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DecisionsStreamOpts struct {
|
||||||
|
Startup bool `url:"startup,omitempty"`
|
||||||
|
Scopes string `url:"scopes,omitempty"`
|
||||||
|
ScenariosContaining string `url:"scenarios_containing,omitempty"`
|
||||||
|
ScenariosNotContaining string `url:"scenarios_not_containing,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DecisionsStreamOpts) addQueryParamsToURL(url string) (string, error) {
|
||||||
|
params, err := qs.Values(o)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s?%s", url, params.Encode()), nil
|
||||||
|
}
|
||||||
|
|
||||||
type DecisionsDeleteOpts struct {
|
type DecisionsDeleteOpts struct {
|
||||||
ScopeEquals *string `url:"scope,omitempty"`
|
ScopeEquals *string `url:"scope,omitempty"`
|
||||||
ValueEquals *string `url:"value,omitempty"`
|
ValueEquals *string `url:"value,omitempty"`
|
||||||
|
@ -53,11 +66,11 @@ func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*m
|
||||||
return &decisions, resp, nil
|
return &decisions, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DecisionsService) GetStream(ctx context.Context, startup bool, scopes []string) (*models.DecisionsStreamResponse, *Response, error) {
|
func (s *DecisionsService) GetStream(ctx context.Context, opts DecisionsStreamOpts) (*models.DecisionsStreamResponse, *Response, error) {
|
||||||
var decisions models.DecisionsStreamResponse
|
var decisions models.DecisionsStreamResponse
|
||||||
u := fmt.Sprintf("%s/decisions/stream?startup=%t", s.client.URLPrefix, startup)
|
u, err := opts.addQueryParamsToURL(s.client.URLPrefix + "/decisions/stream")
|
||||||
if len(scopes) > 0 {
|
if err != nil {
|
||||||
u += "&scopes=" + strings.Join(scopes, ",")
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
req, err := s.client.NewRequest("GET", u, nil)
|
req, err := s.client.NewRequest("GET", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -160,7 +160,7 @@ func TestDecisionsStream(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), true, []string{})
|
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
@ -175,7 +175,7 @@ func TestDecisionsStream(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//and second call, we get empty lists
|
//and second call, we get empty lists
|
||||||
decisions, resp, err = newcli.Decisions.GetStream(context.Background(), false, []string{})
|
decisions, resp, err = newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: false})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if resp.Response.StatusCode != http.StatusOK {
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
@ -234,6 +234,73 @@ func TestDeleteDecisions(t *testing.T) {
|
||||||
defer teardown()
|
defer teardown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
|
||||||
|
baseURLString := "http://localhost:8080/v1/decisions/stream"
|
||||||
|
type fields struct {
|
||||||
|
Startup bool
|
||||||
|
Scopes string
|
||||||
|
ScenariosContaining string
|
||||||
|
ScenariosNotContaining string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no filter",
|
||||||
|
want: baseURLString + "?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "startup=true",
|
||||||
|
fields: fields{
|
||||||
|
Startup: true,
|
||||||
|
},
|
||||||
|
want: baseURLString + "?startup=true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set all params",
|
||||||
|
fields: fields{
|
||||||
|
Startup: true,
|
||||||
|
Scopes: "ip,range",
|
||||||
|
ScenariosContaining: "ssh",
|
||||||
|
ScenariosNotContaining: "bf",
|
||||||
|
},
|
||||||
|
want: baseURLString + "?scenarios_containing=ssh&scenarios_not_containing=bf&scopes=ip%2Crange&startup=true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
o := &DecisionsStreamOpts{
|
||||||
|
Startup: tt.fields.Startup,
|
||||||
|
Scopes: tt.fields.Scopes,
|
||||||
|
ScenariosContaining: tt.fields.ScenariosContaining,
|
||||||
|
ScenariosNotContaining: tt.fields.ScenariosNotContaining,
|
||||||
|
}
|
||||||
|
got, err := o.addQueryParamsToURL(baseURLString)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gotURL, err := url.Parse(got)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedURL, err := url.Parse(tt.want)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *gotURL != *expectedURL {
|
||||||
|
t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() = %v, want %v", *gotURL, *expectedURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// func TestDeleteOneDecision(t *testing.T) {
|
// func TestDeleteOneDecision(t *testing.T) {
|
||||||
// mux, urlx, teardown := setup()
|
// mux, urlx, teardown := setup()
|
||||||
// mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
// mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -275,7 +275,7 @@ func (a *apic) PullTop() error {
|
||||||
log.Printf("last CAPI pull is newer than 1h30, skip.")
|
log.Printf("last CAPI pull is newer than 1h30, skip.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, _, err := a.apiClient.Decisions.GetStream(context.Background(), a.startup, []string{})
|
data, _, err := a.apiClient.Decisions.GetStream(context.Background(), apiclient.DecisionsStreamOpts{Startup: a.startup})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "get stream")
|
return errors.Wrap(err, "get stream")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||||
|
@ -127,10 +126,9 @@ func (c *Controller) StreamDecision(gctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filters := make(map[string][]string)
|
filters := gctx.Request.URL.Query()
|
||||||
filters["scope"] = []string{"ip", "range"}
|
if _, ok := filters["scopes"]; !ok {
|
||||||
if val, ok := gctx.Request.URL.Query()["scopes"]; ok {
|
filters["scopes"] = []string{"ip,range"}
|
||||||
filters["scope"] = strings.Split(val[0], ",")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the blocker just start, return all decisions
|
// if the blocker just start, return all decisions
|
||||||
|
|
|
@ -120,7 +120,7 @@ func TestDeleteDecisionFilter(t *testing.T) {
|
||||||
|
|
||||||
// delete by scope/value
|
// delete by scope/value
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest("DELETE", "/v1/decisions?scope=Ip&value=91.121.79.178", strings.NewReader(""))
|
req, _ = http.NewRequest("DELETE", "/v1/decisions?scopes=Ip&value=91.121.79.178", strings.NewReader(""))
|
||||||
AddAuthHeaders(req, loginResp)
|
AddAuthHeaders(req, loginResp)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
@ -185,7 +185,7 @@ func TestGetDecisionFilters(t *testing.T) {
|
||||||
|
|
||||||
// Get Decision : scope/value
|
// Get Decision : scope/value
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest("GET", "/v1/decisions?scope=Ip&value=91.121.79.179", strings.NewReader(""))
|
req, _ = http.NewRequest("GET", "/v1/decisions?scopes=Ip&value=91.121.79.179", strings.NewReader(""))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
req.Header.Add("X-Api-Key", APIKey)
|
req.Header.Add("X-Api-Key", APIKey)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
@ -249,19 +249,19 @@ func TestGetDecision(t *testing.T) {
|
||||||
log.Fatalf("%s", err.Error())
|
log.Fatalf("%s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Decision with invalid filter
|
// Get Decision
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest("GET", "/v1/decisions?test=test", strings.NewReader(""))
|
req, _ = http.NewRequest("GET", "/v1/decisions", strings.NewReader(""))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
req.Header.Add("X-Api-Key", APIKey)
|
req.Header.Add("X-Api-Key", APIKey)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"'test' doesn't exist: invalid filter\"}", w.Body.String())
|
assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]")
|
||||||
|
|
||||||
// Get Decision
|
// Get Decision with invalid filter. It should ignore this filter
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest("GET", "/v1/decisions", strings.NewReader(""))
|
req, _ = http.NewRequest("GET", "/v1/decisions?test=test", strings.NewReader(""))
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
req.Header.Add("X-Api-Key", APIKey)
|
req.Header.Add("X-Api-Key", APIKey)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
@ -388,7 +388,7 @@ func TestStreamDecision(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Valid Alert
|
// Create Valid Alert
|
||||||
alertContentBytes, err := ioutil.ReadFile("./tests/alert_sample.json")
|
alertContentBytes, err := ioutil.ReadFile("./tests/alert_stream_fixture.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -432,5 +432,51 @@ func TestStreamDecision(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"Ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]}")
|
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\"")
|
||||||
|
|
||||||
|
// 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\"")
|
||||||
|
|
||||||
|
// 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\"")
|
||||||
|
|
||||||
|
// 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\"")
|
||||||
|
|
||||||
|
// 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\"")
|
||||||
}
|
}
|
||||||
|
|
173
pkg/apiserver/tests/alert_stream_fixture.json
Normal file
173
pkg/apiserver/tests/alert_stream_fixture.json
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"machine_id": "test",
|
||||||
|
"capacity": 1,
|
||||||
|
"created_at": "2020-10-09T10:00:10Z",
|
||||||
|
"decisions": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"duration": "1h",
|
||||||
|
"origin": "test1",
|
||||||
|
"scenario": "crowdsecurity/http_bf",
|
||||||
|
"scope": "Ip",
|
||||||
|
"value": "127.0.0.1",
|
||||||
|
"type": "ban"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Events": [
|
||||||
|
{
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2020-10-09T10:00:01Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events_count": 1,
|
||||||
|
"labels": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"leakspeed": "0.5s",
|
||||||
|
"message": "test",
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scenario": "crowdsecurity/http_bf",
|
||||||
|
"scenario_hash": "hashtest",
|
||||||
|
"scenario_version": "v1",
|
||||||
|
"simulated": false,
|
||||||
|
"source": {
|
||||||
|
"as_name": "test",
|
||||||
|
"as_number": "0123456",
|
||||||
|
"cn": "france",
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"latitude": 46.227638,
|
||||||
|
"logitude": 2.213749,
|
||||||
|
"range": "127.0.0.1/32",
|
||||||
|
"scope": "ip",
|
||||||
|
"value": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"start_at": "2020-10-09T10:00:01Z",
|
||||||
|
"stop_at": "2020-10-09T10:00:05Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"machine_id": "test",
|
||||||
|
"capacity": 1,
|
||||||
|
"created_at": "2020-10-09T10:00:10Z",
|
||||||
|
"decisions": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"duration": "1h",
|
||||||
|
"origin": "test2",
|
||||||
|
"scenario": "crowdsecurity/ssh_bf",
|
||||||
|
"scope": "Ip",
|
||||||
|
"value": "127.0.0.1",
|
||||||
|
"type": "ban"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Events": [
|
||||||
|
{
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2020-10-09T10:00:01Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events_count": 1,
|
||||||
|
"labels": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"leakspeed": "0.5s",
|
||||||
|
"message": "test",
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scenario": "crowdsecurity/ssh_bf",
|
||||||
|
"scenario_hash": "hashtest",
|
||||||
|
"scenario_version": "v1",
|
||||||
|
"simulated": false,
|
||||||
|
"source": {
|
||||||
|
"as_name": "test",
|
||||||
|
"as_number": "0123456",
|
||||||
|
"cn": "france",
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"latitude": 46.227638,
|
||||||
|
"logitude": 2.213749,
|
||||||
|
"range": "127.0.0.1/32",
|
||||||
|
"scope": "ip",
|
||||||
|
"value": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"start_at": "2020-10-09T10:00:01Z",
|
||||||
|
"stop_at": "2020-10-09T10:00:05Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"machine_id": "test",
|
||||||
|
"capacity": 1,
|
||||||
|
"created_at": "2020-10-09T10:00:10Z",
|
||||||
|
"decisions": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"duration": "1h",
|
||||||
|
"origin": "test3",
|
||||||
|
"scenario": "crowdsecurity/ddos",
|
||||||
|
"scope": "Ip",
|
||||||
|
"value": "127.0.0.1",
|
||||||
|
"type": "ban"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Events": [
|
||||||
|
{
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2020-10-09T10:00:01Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events_count": 1,
|
||||||
|
"labels": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"leakspeed": "0.5s",
|
||||||
|
"message": "test",
|
||||||
|
"meta": [
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"value": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scenario": "crowdsecurity/ddos",
|
||||||
|
"scenario_hash": "hashtest",
|
||||||
|
"scenario_version": "v1",
|
||||||
|
"simulated": false,
|
||||||
|
"source": {
|
||||||
|
"as_name": "test",
|
||||||
|
"as_number": "0123456",
|
||||||
|
"cn": "france",
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"latitude": 46.227638,
|
||||||
|
"logitude": 2.213749,
|
||||||
|
"range": "127.0.0.1/32",
|
||||||
|
"scope": "ip",
|
||||||
|
"value": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"start_at": "2020-10-09T10:00:01Z",
|
||||||
|
"stop_at": "2020-10-09T10:00:05Z"
|
||||||
|
}
|
||||||
|
]
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent/predicate"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -39,31 +40,44 @@ func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string]
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||||
}
|
}
|
||||||
case "scope":
|
case "scopes":
|
||||||
for i, scope := range value {
|
scopes := strings.Split(value[0], ",")
|
||||||
|
for i, scope := range scopes {
|
||||||
switch strings.ToLower(scope) {
|
switch strings.ToLower(scope) {
|
||||||
case "ip":
|
case "ip":
|
||||||
value[i] = types.Ip
|
scopes[i] = types.Ip
|
||||||
case "range":
|
case "range":
|
||||||
value[i] = types.Range
|
scopes[i] = types.Range
|
||||||
case "country":
|
case "country":
|
||||||
value[i] = types.Country
|
scopes[i] = types.Country
|
||||||
case "as":
|
case "as":
|
||||||
value[i] = types.AS
|
scopes[i] = types.AS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query = query.Where(decision.ScopeIn(value...))
|
query = query.Where(decision.ScopeIn(scopes...))
|
||||||
case "value":
|
case "value":
|
||||||
query = query.Where(decision.ValueEQ(value[0]))
|
query = query.Where(decision.ValueEQ(value[0]))
|
||||||
case "type":
|
case "type":
|
||||||
query = query.Where(decision.TypeEQ(value[0]))
|
query = query.Where(decision.TypeEQ(value[0]))
|
||||||
|
case "origins":
|
||||||
|
query = query.Where(
|
||||||
|
decision.OriginIn(strings.Split(value[0], ",")...),
|
||||||
|
)
|
||||||
|
case "scenarios_containing":
|
||||||
|
predicates := decisionPredicatesFromStr(value[0], decision.ScenarioContainsFold)
|
||||||
|
query = query.Where(decision.Or(predicates...))
|
||||||
|
case "scenarios_not_containing":
|
||||||
|
predicates := decisionPredicatesFromStr(value[0], decision.ScenarioContainsFold)
|
||||||
|
query = query.Where(decision.Not(
|
||||||
|
decision.Or(
|
||||||
|
predicates...,
|
||||||
|
),
|
||||||
|
))
|
||||||
case "ip", "range":
|
case "ip", "range":
|
||||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return query, errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +381,7 @@ func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "0", errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
return "0", errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||||
}
|
}
|
||||||
case "scope":
|
case "scopes":
|
||||||
decisions = decisions.Where(decision.ScopeEQ(value[0]))
|
decisions = decisions.Where(decision.ScopeEQ(value[0]))
|
||||||
case "value":
|
case "value":
|
||||||
decisions = decisions.Where(decision.ValueEQ(value[0]))
|
decisions = decisions.Where(decision.ValueEQ(value[0]))
|
||||||
|
@ -474,3 +488,12 @@ func (c *Client) SoftDeleteDecisionByID(decisionID int) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decisionPredicatesFromStr(s string, predicateFunc func(string) predicate.Decision) []predicate.Decision {
|
||||||
|
words := strings.Split(s, ",")
|
||||||
|
predicates := make([]predicate.Decision, len(words))
|
||||||
|
for i, word := range words {
|
||||||
|
predicates[i] = predicateFunc(word)
|
||||||
|
}
|
||||||
|
return predicates
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,21 @@ paths:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
description: 'Comma separated scopes of decisions to fetch'
|
description: 'Comma separated scopes of decisions to fetch'
|
||||||
|
- name: origins
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
description: 'Comma separated name of origins. If provided, then only the decisions originating from provided origins would be returned.'
|
||||||
|
- name: scenarios_containing
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
description: 'Comma separated words. If provided, only the decisions created by scenarios containing any of the provided word would be returned.'
|
||||||
|
- name: scenarios_not_containing
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
description: 'Comma separated words. If provided, only the decisions created by scenarios, not containing any of the provided word would be returned.'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: successful operation
|
description: successful operation
|
||||||
|
|
Loading…
Reference in a new issue