2020-11-30 09:37:17 +00:00
package v1
import (
"net/http"
"strconv"
"time"
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
2022-06-22 08:29:02 +00:00
//Format decisions for the bouncers, and deduplicate them by keeping only the longest one
func FormatDecisions ( decisions [ ] * ent . Decision , dedup bool ) ( [ ] * models . Decision , error ) {
2020-11-30 09:37:17 +00:00
var results [ ] * models . Decision
2022-06-22 08:29:02 +00:00
seen := make ( map [ string ] struct { } , 0 )
2020-11-30 09:37:17 +00:00
for _ , dbDecision := range decisions {
2022-06-22 08:29:02 +00:00
if dedup {
key := dbDecision . Value + dbDecision . Scope + dbDecision . Type
if _ , ok := seen [ key ] ; ok {
continue
}
seen [ key ] = struct { } { }
}
2022-01-19 13:56:05 +00:00
duration := dbDecision . Until . Sub ( time . Now ( ) . UTC ( ) ) . String ( )
2020-11-30 09:37:17 +00:00
decision := models . Decision {
ID : int64 ( dbDecision . ID ) ,
Duration : & duration ,
Scenario : & dbDecision . Scenario ,
Scope : & dbDecision . Scope ,
Value : & dbDecision . Value ,
Type : & dbDecision . Type ,
Origin : & dbDecision . Origin ,
2023-01-31 13:47:44 +00:00
UUID : dbDecision . UUID ,
2020-11-30 09:37:17 +00:00
}
results = append ( results , & decision )
}
return results , nil
}
func ( c * Controller ) GetDecision ( gctx * gin . Context ) {
var err error
var results [ ] * models . Decision
var data [ ] * ent . Decision
2022-05-27 13:23:59 +00:00
bouncerInfo , err := getBouncerFromContext ( gctx )
if err != nil {
gctx . JSON ( http . StatusUnauthorized , gin . H { "message" : "not allowed" } )
return
}
2020-11-30 09:37:17 +00:00
data , err = c . DBClient . QueryDecisionWithFilter ( gctx . Request . URL . Query ( ) )
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
2022-06-22 08:29:02 +00:00
results , err = FormatDecisions ( data , false )
2020-11-30 09:37:17 +00:00
if err != nil {
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
/ * let ' s follow a naive logic : when a bouncer queries / decisions , if the answer is empty , we assume there is no decision for this ip / user / ... ,
but if it ' s non - empty , it means that there is one or more decisions for this target * /
if len ( results ) > 0 {
PrometheusBouncersHasNonEmptyDecision ( gctx )
} else {
PrometheusBouncersHasEmptyDecision ( gctx )
}
2022-09-06 11:55:03 +00:00
if gctx . Request . Method == http . MethodHead {
2020-11-30 09:37:17 +00:00
gctx . String ( http . StatusOK , "" )
return
}
2022-05-27 13:23:59 +00:00
if time . Now ( ) . UTC ( ) . Sub ( bouncerInfo . LastPull ) >= time . Minute {
if err := c . DBClient . UpdateBouncerLastPull ( time . Now ( ) . UTC ( ) , bouncerInfo . ID ) ; err != nil {
log . Errorf ( "failed to update bouncer last pull: %v" , err )
}
}
2020-11-30 09:37:17 +00:00
gctx . JSON ( http . StatusOK , results )
}
func ( c * Controller ) DeleteDecisionById ( gctx * gin . Context ) {
var err error
decisionIDStr := gctx . Param ( "decision_id" )
decisionID , err := strconv . Atoi ( decisionIDStr )
if err != nil {
gctx . JSON ( http . StatusBadRequest , gin . H { "message" : "decision_id must be valid integer" } )
return
}
2023-01-31 13:47:44 +00:00
nbDeleted , deletedFromDB , err := c . DBClient . SoftDeleteDecisionByID ( decisionID )
2020-11-30 09:37:17 +00:00
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
2023-01-31 13:47:44 +00:00
//transform deleted decisions to be sendable to capi
deletedDecisions , err := FormatDecisions ( deletedFromDB , false )
if err != nil {
log . Warningf ( "failed to format decisions: %v" , err )
}
if c . DecisionDeleteChan != nil {
c . DecisionDeleteChan <- deletedDecisions
}
2020-11-30 09:37:17 +00:00
deleteDecisionResp := models . DeleteDecisionResponse {
2022-06-22 08:29:02 +00:00
NbDeleted : strconv . Itoa ( nbDeleted ) ,
2020-11-30 09:37:17 +00:00
}
gctx . JSON ( http . StatusOK , deleteDecisionResp )
}
func ( c * Controller ) DeleteDecisions ( gctx * gin . Context ) {
var err error
2023-01-31 13:47:44 +00:00
nbDeleted , deletedFromDB , err := c . DBClient . SoftDeleteDecisionsWithFilter ( gctx . Request . URL . Query ( ) )
2020-11-30 09:37:17 +00:00
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
2023-01-31 13:47:44 +00:00
//transform deleted decisions to be sendable to capi
deletedDecisions , err := FormatDecisions ( deletedFromDB , false )
if err != nil {
log . Warningf ( "failed to format decisions: %v" , err )
}
if c . DecisionDeleteChan != nil {
c . DecisionDeleteChan <- deletedDecisions
}
2020-11-30 09:37:17 +00:00
deleteDecisionResp := models . DeleteDecisionResponse {
NbDeleted : nbDeleted ,
}
gctx . JSON ( http . StatusOK , deleteDecisionResp )
}
func ( c * Controller ) StreamDecision ( gctx * gin . Context ) {
var data [ ] * ent . Decision
2022-05-27 13:23:59 +00:00
var err error
2020-11-30 09:37:17 +00:00
ret := make ( map [ string ] [ ] * models . Decision , 0 )
ret [ "new" ] = [ ] * models . Decision { }
ret [ "deleted" ] = [ ] * models . Decision { }
2022-05-27 13:23:59 +00:00
streamStartTime := time . Now ( ) . UTC ( )
2020-11-30 09:37:17 +00:00
2022-05-27 13:23:59 +00:00
bouncerInfo , err := getBouncerFromContext ( gctx )
2020-11-30 09:37:17 +00:00
if err != nil {
gctx . JSON ( http . StatusUnauthorized , gin . H { "message" : "not allowed" } )
return
}
2022-03-16 13:37:42 +00:00
filters := gctx . Request . URL . Query ( )
if _ , ok := filters [ "scopes" ] ; ! ok {
filters [ "scopes" ] = [ ] string { "ip,range" }
2021-05-31 13:07:09 +00:00
}
2022-08-26 12:17:46 +00:00
dedup := true
if v , ok := filters [ "dedup" ] ; ok && v [ 0 ] == "false" {
dedup = false
}
2020-11-30 09:37:17 +00:00
// if the blocker just start, return all decisions
if val , ok := gctx . Request . URL . Query ( ) [ "startup" ] ; ok {
if val [ 0 ] == "true" {
2022-06-16 12:41:54 +00:00
data , err = c . DBClient . QueryAllDecisionsWithFilters ( filters )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "failed querying decisions: %v" , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-06-22 08:29:02 +00:00
//data = KeepLongestDecision(data)
2022-08-26 12:17:46 +00:00
ret [ "new" ] , err = FormatDecisions ( data , dedup )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to format expired decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
// getting expired decisions
2021-05-31 13:07:09 +00:00
data , err = c . DBClient . QueryExpiredDecisionsWithFilters ( filters )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to query expired decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-08-26 12:17:46 +00:00
ret [ "deleted" ] , err = FormatDecisions ( data , dedup )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to format expired decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-05-27 13:23:59 +00:00
if err := c . DBClient . UpdateBouncerLastPull ( streamStartTime , bouncerInfo . ID ) ; err != nil {
2020-11-30 09:37:17 +00:00
log . Errorf ( "unable to update bouncer '%s' pull: %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-09-06 11:55:03 +00:00
if gctx . Request . Method == http . MethodHead {
2020-11-30 09:37:17 +00:00
gctx . String ( http . StatusOK , "" )
return
}
gctx . JSON ( http . StatusOK , ret )
return
}
}
// getting new decisions
2021-05-31 13:07:09 +00:00
data , err = c . DBClient . QueryNewDecisionsSinceWithFilters ( bouncerInfo . LastPull , filters )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to query new decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-06-22 08:29:02 +00:00
//data = KeepLongestDecision(data)
2022-08-26 12:17:46 +00:00
ret [ "new" ] , err = FormatDecisions ( data , dedup )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to format new decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
// getting expired decisions
2021-05-31 13:07:09 +00:00
data , err = c . DBClient . QueryExpiredDecisionsSinceWithFilters ( bouncerInfo . LastPull . Add ( ( - 2 * time . Second ) ) , filters ) // do we want to give exactly lastPull time ?
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to query expired decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-08-26 12:17:46 +00:00
ret [ "deleted" ] , err = FormatDecisions ( data , dedup )
2020-11-30 09:37:17 +00:00
if err != nil {
log . Errorf ( "unable to format expired decision for '%s' : %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
2022-05-27 13:23:59 +00:00
if err := c . DBClient . UpdateBouncerLastPull ( streamStartTime , bouncerInfo . ID ) ; err != nil {
2020-11-30 09:37:17 +00:00
log . Errorf ( "unable to update bouncer '%s' pull: %v" , bouncerInfo . Name , err )
gctx . JSON ( http . StatusInternalServerError , gin . H { "message" : err . Error ( ) } )
return
}
gctx . JSON ( http . StatusOK , ret )
}