support decisions deletion via scenario + alerts delete via ID (#1798)
This commit is contained in:
parent
24b540ecde
commit
ae6bf39495
10 changed files with 153 additions and 12 deletions
|
@ -372,11 +372,12 @@ cscli decisions add --scope username --value foobar
|
|||
cmdDecisions.AddCommand(cmdDecisionsAdd)
|
||||
|
||||
var delFilter = apiclient.DecisionsDeleteOpts{
|
||||
ScopeEquals: new(string),
|
||||
ValueEquals: new(string),
|
||||
TypeEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
ScopeEquals: new(string),
|
||||
ValueEquals: new(string),
|
||||
TypeEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
ScenarioEquals: new(string),
|
||||
}
|
||||
var delDecisionId string
|
||||
var delDecisionAll bool
|
||||
|
@ -397,7 +398,7 @@ cscli decisions delete --type captcha
|
|||
}
|
||||
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
|
||||
*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
|
||||
*delFilter.RangeEquals == "" && delDecisionId == "" {
|
||||
*delFilter.RangeEquals == "" && *delFilter.ScenarioEquals == "" && delDecisionId == "" {
|
||||
cmd.Usage()
|
||||
log.Fatalln("At least one filter or --all must be specified")
|
||||
}
|
||||
|
@ -416,6 +417,9 @@ cscli decisions delete --type captcha
|
|||
if *delFilter.ValueEquals == "" {
|
||||
delFilter.ValueEquals = nil
|
||||
}
|
||||
if *delFilter.ScenarioEquals == "" {
|
||||
delFilter.ScenarioEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.TypeEquals == "" {
|
||||
delFilter.TypeEquals = nil
|
||||
|
@ -453,9 +457,10 @@ cscli decisions delete --type captcha
|
|||
cmdDecisionsDelete.Flags().SortFlags = false
|
||||
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().StringVar(&delDecisionId, "id", "", "decision id")
|
||||
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.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(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
|
||||
}
|
||||
|
||||
//to demo query arguments
|
||||
// to demo query arguments
|
||||
func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.GetAlertsResponse, *Response, error) {
|
||||
var alerts models.GetAlertsResponse
|
||||
var URI string
|
||||
|
@ -92,7 +92,7 @@ func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.
|
|||
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) {
|
||||
var alerts models.DeleteAlertsResponse
|
||||
params, err := qs.Values(opts)
|
||||
|
@ -113,6 +113,22 @@ func (s *AlertsService) Delete(ctx context.Context, opts AlertsDeleteOpts) (*mod
|
|||
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) {
|
||||
var alert models.Alert
|
||||
u := fmt.Sprintf("%s/alerts/%d", s.client.URLPrefix, alertID)
|
||||
|
|
|
@ -44,10 +44,12 @@ type DecisionsDeleteOpts struct {
|
|||
IPEquals *string `url:"ip,omitempty"`
|
||||
RangeEquals *string `url:"range,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
//
|
||||
ScenarioEquals *string `url:"scenario,omitempty"`
|
||||
ListOpts
|
||||
}
|
||||
|
||||
//to demo query arguments
|
||||
// to demo query arguments
|
||||
func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*models.GetDecisionsResponse, *Response, error) {
|
||||
var decisions models.GetDecisionsResponse
|
||||
params, err := qs.Values(opts)
|
||||
|
|
|
@ -419,6 +419,29 @@ func TestDeleteAlert(t *testing.T) {
|
|||
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) {
|
||||
cfg := LoadTestConfig()
|
||||
// IPv6 mocking doesn't seem to work.
|
||||
|
|
|
@ -95,6 +95,7 @@ func (c *Controller) NewV1() error {
|
|||
jwtAuth.HEAD("/alerts", c.HandlerV1.FindAlerts)
|
||||
jwtAuth.GET("/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("/decisions", c.HandlerV1.DeleteDecisions)
|
||||
jwtAuth.DELETE("/decisions/:decision_id", c.HandlerV1.DeleteDecisionById)
|
||||
|
|
|
@ -239,6 +239,36 @@ func (c *Controller) FindAlertByID(gctx *gin.Context) {
|
|||
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
|
||||
func (c *Controller) DeleteAlerts(gctx *gin.Context) {
|
||||
incomingIP := gctx.ClientIP()
|
||||
|
|
|
@ -61,6 +61,25 @@ func TestDeleteDecisionFilter(t *testing.T) {
|
|||
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) {
|
||||
lapi := SetupLAPITest(t)
|
||||
|
||||
|
|
|
@ -909,6 +909,15 @@ func (c *Client) DeleteAlertGraph(alertItem *ent.Alert) error {
|
|||
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) {
|
||||
preds, err := AlertPredicatesFromFilter(filter)
|
||||
if err != nil {
|
||||
|
|
|
@ -305,6 +305,8 @@ func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string,
|
|||
if err != nil {
|
||||
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:
|
||||
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 {
|
||||
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:
|
||||
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
|
||||
}
|
||||
|
||||
//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) {
|
||||
nbUpdated, err := c.Ent.Decision.Update().Where(decision.IDEQ(decisionID)).SetUntil(time.Now().UTC()).Save(c.CTX)
|
||||
if err != nil || nbUpdated == 0 {
|
||||
|
|
|
@ -242,6 +242,11 @@ paths:
|
|||
required: false
|
||||
type: string
|
||||
description: range to search for (shorthand for scope=range&value=)
|
||||
- name: scenario
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
description: scenario to search
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -256,7 +261,7 @@ paths:
|
|||
- JWTAuthorizer: []
|
||||
'/decisions/{decision_id}':
|
||||
delete:
|
||||
description: Delete decision for given ban ID (only from cscli)
|
||||
description: Delete decision for given decision ID (only from cscli)
|
||||
summary: DeleteDecision
|
||||
tags:
|
||||
- watchers
|
||||
|
@ -652,6 +657,33 @@ paths:
|
|||
description: "400 response"
|
||||
security:
|
||||
- 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:
|
||||
WatcherRegistrationRequest:
|
||||
title: WatcherRegistrationRequest
|
||||
|
|
Loading…
Add table
Reference in a new issue