decisions.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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. // if the blocker just start, return all decisions
  121. if val, ok := gctx.Request.URL.Query()["startup"]; ok {
  122. if val[0] == "true" {
  123. data, err = c.DBClient.QueryAllDecisionsWithFilters(filters)
  124. if err != nil {
  125. log.Errorf("failed querying decisions: %v", err)
  126. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  127. return
  128. }
  129. //data = KeepLongestDecision(data)
  130. ret["new"], err = FormatDecisions(data, true)
  131. if err != nil {
  132. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  133. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  134. return
  135. }
  136. // getting expired decisions
  137. data, err = c.DBClient.QueryExpiredDecisionsWithFilters(filters)
  138. if err != nil {
  139. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  140. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  141. return
  142. }
  143. ret["deleted"], err = FormatDecisions(data, true)
  144. if err != nil {
  145. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  146. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  147. return
  148. }
  149. if err := c.DBClient.UpdateBouncerLastPull(streamStartTime, bouncerInfo.ID); err != nil {
  150. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  151. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  152. return
  153. }
  154. if gctx.Request.Method == "HEAD" {
  155. gctx.String(http.StatusOK, "")
  156. return
  157. }
  158. gctx.JSON(http.StatusOK, ret)
  159. return
  160. }
  161. }
  162. // getting new decisions
  163. data, err = c.DBClient.QueryNewDecisionsSinceWithFilters(bouncerInfo.LastPull, filters)
  164. if err != nil {
  165. log.Errorf("unable to query new decision for '%s' : %v", bouncerInfo.Name, err)
  166. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  167. return
  168. }
  169. //data = KeepLongestDecision(data)
  170. ret["new"], err = FormatDecisions(data, true)
  171. if err != nil {
  172. log.Errorf("unable to format new decision for '%s' : %v", bouncerInfo.Name, err)
  173. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  174. return
  175. }
  176. // getting expired decisions
  177. data, err = c.DBClient.QueryExpiredDecisionsSinceWithFilters(bouncerInfo.LastPull.Add((-2 * time.Second)), filters) // do we want to give exactly lastPull time ?
  178. if err != nil {
  179. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  180. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  181. return
  182. }
  183. ret["deleted"], err = FormatDecisions(data, true)
  184. if err != nil {
  185. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  186. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  187. return
  188. }
  189. if err := c.DBClient.UpdateBouncerLastPull(streamStartTime, bouncerInfo.ID); err != nil {
  190. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  191. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  192. return
  193. }
  194. gctx.JSON(http.StatusOK, ret)
  195. }