decisions.go 6.3 KB

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