decisions.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package v1
  2. import (
  3. "net/http"
  4. "strconv"
  5. "time"
  6. "github.com/crowdsecurity/crowdsec/pkg/database/ent"
  7. "github.com/crowdsecurity/crowdsec/pkg/models"
  8. "github.com/gin-gonic/gin"
  9. log "github.com/sirupsen/logrus"
  10. )
  11. //Format decisions for the bouncers, and deduplicate them by keeping only the longest one
  12. func FormatDecisions(decisions []*ent.Decision, dedup bool) ([]*models.Decision, error) {
  13. var results []*models.Decision
  14. seen := make(map[string]struct{}, 0)
  15. for _, dbDecision := range decisions {
  16. if dedup {
  17. key := dbDecision.Value + dbDecision.Scope + dbDecision.Type
  18. if _, ok := seen[key]; ok {
  19. continue
  20. }
  21. seen[key] = struct{}{}
  22. }
  23. duration := dbDecision.Until.Sub(time.Now().UTC()).String()
  24. decision := models.Decision{
  25. ID: int64(dbDecision.ID),
  26. Duration: &duration,
  27. Scenario: &dbDecision.Scenario,
  28. Scope: &dbDecision.Scope,
  29. Value: &dbDecision.Value,
  30. Type: &dbDecision.Type,
  31. Origin: &dbDecision.Origin,
  32. }
  33. results = append(results, &decision)
  34. }
  35. return results, nil
  36. }
  37. func (c *Controller) GetDecision(gctx *gin.Context) {
  38. var err error
  39. var results []*models.Decision
  40. var data []*ent.Decision
  41. bouncerInfo, err := getBouncerFromContext(gctx)
  42. if err != nil {
  43. gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
  44. return
  45. }
  46. data, err = c.DBClient.QueryDecisionWithFilter(gctx.Request.URL.Query())
  47. if err != nil {
  48. c.HandleDBErrors(gctx, err)
  49. return
  50. }
  51. results, err = FormatDecisions(data, false)
  52. if err != nil {
  53. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  54. return
  55. }
  56. /*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/...,
  57. but if it's non-empty, it means that there is one or more decisions for this target*/
  58. if len(results) > 0 {
  59. PrometheusBouncersHasNonEmptyDecision(gctx)
  60. } else {
  61. PrometheusBouncersHasEmptyDecision(gctx)
  62. }
  63. if gctx.Request.Method == "HEAD" {
  64. gctx.String(http.StatusOK, "")
  65. return
  66. }
  67. if time.Now().UTC().Sub(bouncerInfo.LastPull) >= time.Minute {
  68. if err := c.DBClient.UpdateBouncerLastPull(time.Now().UTC(), bouncerInfo.ID); err != nil {
  69. log.Errorf("failed to update bouncer last pull: %v", err)
  70. }
  71. }
  72. gctx.JSON(http.StatusOK, results)
  73. }
  74. func (c *Controller) DeleteDecisionById(gctx *gin.Context) {
  75. var err error
  76. decisionIDStr := gctx.Param("decision_id")
  77. decisionID, err := strconv.Atoi(decisionIDStr)
  78. if err != nil {
  79. gctx.JSON(http.StatusBadRequest, gin.H{"message": "decision_id must be valid integer"})
  80. return
  81. }
  82. nbDeleted, err := c.DBClient.SoftDeleteDecisionByID(decisionID)
  83. if err != nil {
  84. c.HandleDBErrors(gctx, err)
  85. return
  86. }
  87. deleteDecisionResp := models.DeleteDecisionResponse{
  88. NbDeleted: strconv.Itoa(nbDeleted),
  89. }
  90. gctx.JSON(http.StatusOK, deleteDecisionResp)
  91. }
  92. func (c *Controller) DeleteDecisions(gctx *gin.Context) {
  93. var err error
  94. nbDeleted, err := c.DBClient.SoftDeleteDecisionsWithFilter(gctx.Request.URL.Query())
  95. if err != nil {
  96. c.HandleDBErrors(gctx, err)
  97. return
  98. }
  99. deleteDecisionResp := models.DeleteDecisionResponse{
  100. NbDeleted: nbDeleted,
  101. }
  102. gctx.JSON(http.StatusOK, deleteDecisionResp)
  103. }
  104. func (c *Controller) StreamDecision(gctx *gin.Context) {
  105. var data []*ent.Decision
  106. var err error
  107. ret := make(map[string][]*models.Decision, 0)
  108. ret["new"] = []*models.Decision{}
  109. ret["deleted"] = []*models.Decision{}
  110. streamStartTime := time.Now().UTC()
  111. bouncerInfo, err := getBouncerFromContext(gctx)
  112. if err != nil {
  113. gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
  114. return
  115. }
  116. filters := gctx.Request.URL.Query()
  117. if _, ok := filters["scopes"]; !ok {
  118. filters["scopes"] = []string{"ip,range"}
  119. }
  120. dedup := true
  121. if v, ok := filters["dedup"]; ok && v[0] == "false" {
  122. dedup = false
  123. }
  124. // if the blocker just start, return all decisions
  125. if val, ok := gctx.Request.URL.Query()["startup"]; ok {
  126. if val[0] == "true" {
  127. data, err = c.DBClient.QueryAllDecisionsWithFilters(filters)
  128. if err != nil {
  129. log.Errorf("failed querying decisions: %v", err)
  130. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  131. return
  132. }
  133. //data = KeepLongestDecision(data)
  134. ret["new"], err = FormatDecisions(data, dedup)
  135. if err != nil {
  136. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  137. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  138. return
  139. }
  140. // getting expired decisions
  141. data, err = c.DBClient.QueryExpiredDecisionsWithFilters(filters)
  142. if err != nil {
  143. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  144. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  145. return
  146. }
  147. ret["deleted"], err = FormatDecisions(data, dedup)
  148. if err != nil {
  149. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  150. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  151. return
  152. }
  153. if err := c.DBClient.UpdateBouncerLastPull(streamStartTime, bouncerInfo.ID); err != nil {
  154. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  155. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  156. return
  157. }
  158. if gctx.Request.Method == "HEAD" {
  159. gctx.String(http.StatusOK, "")
  160. return
  161. }
  162. gctx.JSON(http.StatusOK, ret)
  163. return
  164. }
  165. }
  166. // getting new decisions
  167. data, err = c.DBClient.QueryNewDecisionsSinceWithFilters(bouncerInfo.LastPull, filters)
  168. if err != nil {
  169. log.Errorf("unable to query new decision for '%s' : %v", bouncerInfo.Name, err)
  170. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  171. return
  172. }
  173. //data = KeepLongestDecision(data)
  174. ret["new"], err = FormatDecisions(data, dedup)
  175. if err != nil {
  176. log.Errorf("unable to format new decision for '%s' : %v", bouncerInfo.Name, err)
  177. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  178. return
  179. }
  180. // getting expired decisions
  181. data, err = c.DBClient.QueryExpiredDecisionsSinceWithFilters(bouncerInfo.LastPull.Add((-2 * time.Second)), filters) // do we want to give exactly lastPull time ?
  182. if err != nil {
  183. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  184. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  185. return
  186. }
  187. ret["deleted"], err = FormatDecisions(data, dedup)
  188. if err != nil {
  189. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  190. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  191. return
  192. }
  193. if err := c.DBClient.UpdateBouncerLastPull(streamStartTime, bouncerInfo.ID); err != nil {
  194. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  195. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  196. return
  197. }
  198. gctx.JSON(http.StatusOK, ret)
  199. }