2020-11-30 09:37:17 +00:00
package v1
import (
"crypto/sha512"
"fmt"
"net/http"
"strconv"
2021-05-31 13:07:09 +00:00
"strings"
2020-11-30 09:37:17 +00:00
"time"
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func FormatDecisions ( decisions [ ] * ent . Decision ) ( [ ] * models . Decision , error ) {
var results [ ] * models . Decision
for _ , dbDecision := range decisions {
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 ,
}
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
data , err = c . DBClient . QueryDecisionWithFilter ( gctx . Request . URL . Query ( ) )
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
results , err = FormatDecisions ( data )
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 )
}
if gctx . Request . Method == "HEAD" {
gctx . String ( http . StatusOK , "" )
return
}
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
}
err = c . DBClient . SoftDeleteDecisionByID ( decisionID )
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
deleteDecisionResp := models . DeleteDecisionResponse {
NbDeleted : "1" ,
}
gctx . JSON ( http . StatusOK , deleteDecisionResp )
return
}
func ( c * Controller ) DeleteDecisions ( gctx * gin . Context ) {
var err error
nbDeleted , err := c . DBClient . SoftDeleteDecisionsWithFilter ( gctx . Request . URL . Query ( ) )
if err != nil {
c . HandleDBErrors ( gctx , err )
return
}
deleteDecisionResp := models . DeleteDecisionResponse {
NbDeleted : nbDeleted ,
}
gctx . JSON ( http . StatusOK , deleteDecisionResp )
return
}
func ( c * Controller ) StreamDecision ( gctx * gin . Context ) {
var data [ ] * ent . Decision
ret := make ( map [ string ] [ ] * models . Decision , 0 )
ret [ "new" ] = [ ] * models . Decision { }
ret [ "deleted" ] = [ ] * models . Decision { }
val := gctx . Request . Header . Get ( c . APIKeyHeader )
hashedKey := sha512 . New ( )
hashedKey . Write ( [ ] byte ( val ) )
hashStr := fmt . Sprintf ( "%x" , hashedKey . Sum ( nil ) )
bouncerInfo , err := c . DBClient . SelectBouncer ( hashStr )
if err != nil {
if _ , ok := err . ( * ent . NotFoundError ) ; ok {
gctx . JSON ( http . StatusForbidden , gin . H { "message" : err . Error ( ) } )
} else {
gctx . JSON ( http . StatusUnauthorized , gin . H { "message" : "not allowed" } )
}
return
}
if bouncerInfo == nil {
gctx . JSON ( http . StatusUnauthorized , gin . H { "message" : "not allowed" } )
return
}
2021-05-31 13:07:09 +00:00
filters := make ( map [ string ] [ ] string )
filters [ "scope" ] = [ ] string { "ip" , "range" }
if val , ok := gctx . Request . URL . Query ( ) [ "scopes" ] ; ok {
filters [ "scope" ] = strings . Split ( val [ 0 ] , "," )
}
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" {
2021-05-31 13:07:09 +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
}
ret [ "new" ] , err = FormatDecisions ( data )
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
}
ret [ "deleted" ] , err = FormatDecisions ( data )
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-01-19 13:56:05 +00:00
if err := c . DBClient . UpdateBouncerLastPull ( time . Now ( ) . UTC ( ) , 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
}
if gctx . Request . Method == "HEAD" {
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
}
ret [ "new" ] , err = FormatDecisions ( data )
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
}
ret [ "deleted" ] , err = FormatDecisions ( data )
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-01-19 13:56:05 +00:00
if err := c . DBClient . UpdateBouncerLastPull ( time . Now ( ) . UTC ( ) , 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 )
return
}