support decisions deletion via scenario + alerts delete via ID (#1798)
This commit is contained in:
parent
24b540ecde
commit
ae6bf39495
|
@ -372,11 +372,12 @@ cscli decisions add --scope username --value foobar
|
||||||
cmdDecisions.AddCommand(cmdDecisionsAdd)
|
cmdDecisions.AddCommand(cmdDecisionsAdd)
|
||||||
|
|
||||||
var delFilter = apiclient.DecisionsDeleteOpts{
|
var delFilter = apiclient.DecisionsDeleteOpts{
|
||||||
ScopeEquals: new(string),
|
ScopeEquals: new(string),
|
||||||
ValueEquals: new(string),
|
ValueEquals: new(string),
|
||||||
TypeEquals: new(string),
|
TypeEquals: new(string),
|
||||||
IPEquals: new(string),
|
IPEquals: new(string),
|
||||||
RangeEquals: new(string),
|
RangeEquals: new(string),
|
||||||
|
ScenarioEquals: new(string),
|
||||||
}
|
}
|
||||||
var delDecisionId string
|
var delDecisionId string
|
||||||
var delDecisionAll bool
|
var delDecisionAll bool
|
||||||
|
@ -397,7 +398,7 @@ cscli decisions delete --type captcha
|
||||||
}
|
}
|
||||||
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
|
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
|
||||||
*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
|
*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
|
||||||
*delFilter.RangeEquals == "" && delDecisionId == "" {
|
*delFilter.RangeEquals == "" && *delFilter.ScenarioEquals == "" && delDecisionId == "" {
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
log.Fatalln("At least one filter or --all must be specified")
|
log.Fatalln("At least one filter or --all must be specified")
|
||||||
}
|
}
|
||||||
|
@ -416,6 +417,9 @@ cscli decisions delete --type captcha
|
||||||
if *delFilter.ValueEquals == "" {
|
if *delFilter.ValueEquals == "" {
|
||||||
delFilter.ValueEquals = nil
|
delFilter.ValueEquals = nil
|
||||||
}
|
}
|
||||||
|
if *delFilter.ScenarioEquals == "" {
|
||||||
|
delFilter.ScenarioEquals = nil
|
||||||
|
}
|
||||||
|
|
||||||
if *delFilter.TypeEquals == "" {
|
if *delFilter.TypeEquals == "" {
|
||||||
delFilter.TypeEquals = nil
|
delFilter.TypeEquals = nil
|
||||||
|
@ -453,9 +457,10 @@ cscli decisions delete --type captcha
|
||||||
cmdDecisionsDelete.Flags().SortFlags = false
|
cmdDecisionsDelete.Flags().SortFlags = false
|
||||||
cmdDecisionsDelete.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
cmdDecisionsDelete.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
||||||
cmdDecisionsDelete.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
cmdDecisionsDelete.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
||||||
cmdDecisionsDelete.Flags().StringVar(&delDecisionId, "id", "", "decision id")
|
|
||||||
cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
|
cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
|
||||||
cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||||
|
cmdDecisionsDelete.Flags().StringVarP(delFilter.ScenarioEquals, "scenario", "s", "", "the scenario name (ie. crowdsecurity/ssh-bf)")
|
||||||
|
cmdDecisionsDelete.Flags().StringVar(&delDecisionId, "id", "", "decision id")
|
||||||
cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
|
cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
|
||||||
cmdDecisionsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
|
cmdDecisionsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (s *AlertsService) Add(ctx context.Context, alerts models.AddAlertsRequest)
|
||||||
return &added_ids, resp, nil
|
return &added_ids, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//to demo query arguments
|
// to demo query arguments
|
||||||
func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.GetAlertsResponse, *Response, error) {
|
func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.GetAlertsResponse, *Response, error) {
|
||||||
var alerts models.GetAlertsResponse
|
var alerts models.GetAlertsResponse
|
||||||
var URI string
|
var URI string
|
||||||
|
@ -92,7 +92,7 @@ func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.
|
||||||
return &alerts, resp, nil
|
return &alerts, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//to demo query arguments
|
// to demo query arguments
|
||||||
func (s *AlertsService) Delete(ctx context.Context, opts AlertsDeleteOpts) (*models.DeleteAlertsResponse, *Response, error) {
|
func (s *AlertsService) Delete(ctx context.Context, opts AlertsDeleteOpts) (*models.DeleteAlertsResponse, *Response, error) {
|
||||||
var alerts models.DeleteAlertsResponse
|
var alerts models.DeleteAlertsResponse
|
||||||
params, err := qs.Values(opts)
|
params, err := qs.Values(opts)
|
||||||
|
@ -113,6 +113,22 @@ func (s *AlertsService) Delete(ctx context.Context, opts AlertsDeleteOpts) (*mod
|
||||||
return &alerts, resp, nil
|
return &alerts, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AlertsService) DeleteOne(ctx context.Context, alert_id string) (*models.DeleteAlertsResponse, *Response, error) {
|
||||||
|
var alerts models.DeleteAlertsResponse
|
||||||
|
u := fmt.Sprintf("%s/alerts/%s", s.client.URLPrefix, alert_id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest(http.MethodDelete, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(ctx, req, &alerts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
return &alerts, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AlertsService) GetByID(ctx context.Context, alertID int) (*models.Alert, *Response, error) {
|
func (s *AlertsService) GetByID(ctx context.Context, alertID int) (*models.Alert, *Response, error) {
|
||||||
var alert models.Alert
|
var alert models.Alert
|
||||||
u := fmt.Sprintf("%s/alerts/%d", s.client.URLPrefix, alertID)
|
u := fmt.Sprintf("%s/alerts/%d", s.client.URLPrefix, alertID)
|
||||||
|
|
|
@ -44,10 +44,12 @@ type DecisionsDeleteOpts 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"`
|
||||||
|
//
|
||||||
|
ScenarioEquals *string `url:"scenario,omitempty"`
|
||||||
ListOpts
|
ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
//to demo query arguments
|
// to demo query arguments
|
||||||
func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*models.GetDecisionsResponse, *Response, error) {
|
func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*models.GetDecisionsResponse, *Response, error) {
|
||||||
var decisions models.GetDecisionsResponse
|
var decisions models.GetDecisionsResponse
|
||||||
params, err := qs.Values(opts)
|
params, err := qs.Values(opts)
|
||||||
|
|
|
@ -419,6 +419,29 @@ func TestDeleteAlert(t *testing.T) {
|
||||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteAlertByID(t *testing.T) {
|
||||||
|
lapi := SetupLAPITest(t)
|
||||||
|
lapi.InsertAlertFromFile("./tests/alert_sample.json")
|
||||||
|
|
||||||
|
// Fail Delete Alert
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(http.MethodDelete, "/v1/alerts/1", strings.NewReader(""))
|
||||||
|
AddAuthHeaders(req, lapi.loginResp)
|
||||||
|
req.RemoteAddr = "127.0.0.2:4242"
|
||||||
|
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(http.MethodDelete, "/v1/alerts/1", strings.NewReader(""))
|
||||||
|
AddAuthHeaders(req, lapi.loginResp)
|
||||||
|
req.RemoteAddr = "127.0.0.1:4242"
|
||||||
|
lapi.router.ServeHTTP(w, req)
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeleteAlertTrustedIPS(t *testing.T) {
|
func TestDeleteAlertTrustedIPS(t *testing.T) {
|
||||||
cfg := LoadTestConfig()
|
cfg := LoadTestConfig()
|
||||||
// IPv6 mocking doesn't seem to work.
|
// IPv6 mocking doesn't seem to work.
|
||||||
|
|
|
@ -95,6 +95,7 @@ func (c *Controller) NewV1() error {
|
||||||
jwtAuth.HEAD("/alerts", c.HandlerV1.FindAlerts)
|
jwtAuth.HEAD("/alerts", c.HandlerV1.FindAlerts)
|
||||||
jwtAuth.GET("/alerts/:alert_id", c.HandlerV1.FindAlertByID)
|
jwtAuth.GET("/alerts/:alert_id", c.HandlerV1.FindAlertByID)
|
||||||
jwtAuth.HEAD("/alerts/:alert_id", c.HandlerV1.FindAlertByID)
|
jwtAuth.HEAD("/alerts/:alert_id", c.HandlerV1.FindAlertByID)
|
||||||
|
jwtAuth.DELETE("/alerts/:alert_id", c.HandlerV1.DeleteAlertByID)
|
||||||
jwtAuth.DELETE("/alerts", c.HandlerV1.DeleteAlerts)
|
jwtAuth.DELETE("/alerts", c.HandlerV1.DeleteAlerts)
|
||||||
jwtAuth.DELETE("/decisions", c.HandlerV1.DeleteDecisions)
|
jwtAuth.DELETE("/decisions", c.HandlerV1.DeleteDecisions)
|
||||||
jwtAuth.DELETE("/decisions/:decision_id", c.HandlerV1.DeleteDecisionById)
|
jwtAuth.DELETE("/decisions/:decision_id", c.HandlerV1.DeleteDecisionById)
|
||||||
|
|
|
@ -239,6 +239,36 @@ func (c *Controller) FindAlertByID(gctx *gin.Context) {
|
||||||
gctx.JSON(http.StatusOK, data)
|
gctx.JSON(http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAlertByID delete the alert associated to the ID
|
||||||
|
func (c *Controller) DeleteAlertByID(gctx *gin.Context) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
incomingIP := gctx.ClientIP()
|
||||||
|
if incomingIP != "127.0.0.1" && incomingIP != "::1" && !networksContainIP(c.TrustedIPs, incomingIP) {
|
||||||
|
gctx.JSON(http.StatusForbidden, gin.H{"message": fmt.Sprintf("access forbidden from this IP (%s)", incomingIP)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decisionIDStr := gctx.Param("alert_id")
|
||||||
|
decisionID, err := strconv.Atoi(decisionIDStr)
|
||||||
|
if err != nil {
|
||||||
|
gctx.JSON(http.StatusBadRequest, gin.H{"message": "alert_id must be valid integer"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.DBClient.DeleteAlertByID(decisionID)
|
||||||
|
if err != nil {
|
||||||
|
c.HandleDBErrors(gctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAlertResp := models.DeleteAlertsResponse{
|
||||||
|
NbDeleted: "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx.JSON(http.StatusOK, deleteAlertResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// DeleteAlerts deletes alerts from the database based on the specified filter
|
// DeleteAlerts deletes alerts from the database based on the specified filter
|
||||||
func (c *Controller) DeleteAlerts(gctx *gin.Context) {
|
func (c *Controller) DeleteAlerts(gctx *gin.Context) {
|
||||||
incomingIP := gctx.ClientIP()
|
incomingIP := gctx.ClientIP()
|
||||||
|
|
|
@ -61,6 +61,25 @@ func TestDeleteDecisionFilter(t *testing.T) {
|
||||||
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteDecisionFilterByScenario(t *testing.T) {
|
||||||
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
|
// Create Valid Alert
|
||||||
|
lapi.InsertAlertFromFile("./tests/alert_minibulk.json")
|
||||||
|
|
||||||
|
// delete by wrong scenario
|
||||||
|
|
||||||
|
w := lapi.RecordResponse("DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bff", emptyBody, PASSWORD)
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, `{"nbDeleted":"0"}`, w.Body.String())
|
||||||
|
|
||||||
|
// delete by scenario good
|
||||||
|
|
||||||
|
w = lapi.RecordResponse("DELETE", "/v1/decisions?scenario=crowdsecurity/ssh-bf", emptyBody, PASSWORD)
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, `{"nbDeleted":"2"}`, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDecisionFilters(t *testing.T) {
|
func TestGetDecisionFilters(t *testing.T) {
|
||||||
lapi := SetupLAPITest(t)
|
lapi := SetupLAPITest(t)
|
||||||
|
|
||||||
|
|
|
@ -909,6 +909,15 @@ func (c *Client) DeleteAlertGraph(alertItem *ent.Alert) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteAlertByID(id int) error {
|
||||||
|
alertItem, err := c.Ent.Alert.Query().Where(alert.IDEQ(id)).Only(c.CTX)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.DeleteAlertGraph(alertItem)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteAlertWithFilter(filter map[string][]string) (int, error) {
|
func (c *Client) DeleteAlertWithFilter(filter map[string][]string) (int, error) {
|
||||||
preds, err := AlertPredicatesFromFilter(filter)
|
preds, err := AlertPredicatesFromFilter(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -305,6 +305,8 @@ func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||||
}
|
}
|
||||||
|
case "scenario":
|
||||||
|
decisions = decisions.Where(decision.ScenarioEQ(value[0]))
|
||||||
default:
|
default:
|
||||||
return "0", errors.Wrap(InvalidFilter, fmt.Sprintf("'%s' doesn't exist", param))
|
return "0", errors.Wrap(InvalidFilter, fmt.Sprintf("'%s' doesn't exist", param))
|
||||||
}
|
}
|
||||||
|
@ -415,6 +417,8 @@ func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||||
}
|
}
|
||||||
|
case "scenario":
|
||||||
|
decisions = decisions.Where(decision.ScenarioEQ(value[0]))
|
||||||
default:
|
default:
|
||||||
return "0", errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
|
return "0", errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
|
||||||
}
|
}
|
||||||
|
@ -498,7 +502,7 @@ func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (stri
|
||||||
return strconv.Itoa(nbDeleted), nil
|
return strconv.Itoa(nbDeleted), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//SoftDeleteDecisionByID set the expiration of a decision to now()
|
// SoftDeleteDecisionByID set the expiration of a decision to now()
|
||||||
func (c *Client) SoftDeleteDecisionByID(decisionID int) (int, error) {
|
func (c *Client) SoftDeleteDecisionByID(decisionID int) (int, error) {
|
||||||
nbUpdated, err := c.Ent.Decision.Update().Where(decision.IDEQ(decisionID)).SetUntil(time.Now().UTC()).Save(c.CTX)
|
nbUpdated, err := c.Ent.Decision.Update().Where(decision.IDEQ(decisionID)).SetUntil(time.Now().UTC()).Save(c.CTX)
|
||||||
if err != nil || nbUpdated == 0 {
|
if err != nil || nbUpdated == 0 {
|
||||||
|
|
|
@ -242,6 +242,11 @@ paths:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
description: range to search for (shorthand for scope=range&value=)
|
description: range to search for (shorthand for scope=range&value=)
|
||||||
|
- name: scenario
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
description: scenario to search
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: successful operation
|
description: successful operation
|
||||||
|
@ -256,7 +261,7 @@ paths:
|
||||||
- JWTAuthorizer: []
|
- JWTAuthorizer: []
|
||||||
'/decisions/{decision_id}':
|
'/decisions/{decision_id}':
|
||||||
delete:
|
delete:
|
||||||
description: Delete decision for given ban ID (only from cscli)
|
description: Delete decision for given decision ID (only from cscli)
|
||||||
summary: DeleteDecision
|
summary: DeleteDecision
|
||||||
tags:
|
tags:
|
||||||
- watchers
|
- watchers
|
||||||
|
@ -652,6 +657,33 @@ paths:
|
||||||
description: "400 response"
|
description: "400 response"
|
||||||
security:
|
security:
|
||||||
- JWTAuthorizer: []
|
- JWTAuthorizer: []
|
||||||
|
delete:
|
||||||
|
description: Delete alert for given alert ID (only from cscli)
|
||||||
|
summary: DeleteAlert
|
||||||
|
tags:
|
||||||
|
- watchers
|
||||||
|
operationId: DeleteAlert
|
||||||
|
deprecated: false
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: ''
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successful operation
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/DeleteAlertsResponse'
|
||||||
|
headers: {}
|
||||||
|
'404':
|
||||||
|
description: "404 response"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ErrorResponse"
|
||||||
|
security:
|
||||||
|
- JWTAuthorizer: []
|
||||||
definitions:
|
definitions:
|
||||||
WatcherRegistrationRequest:
|
WatcherRegistrationRequest:
|
||||||
title: WatcherRegistrationRequest
|
title: WatcherRegistrationRequest
|
||||||
|
|
Loading…
Reference in a new issue