decisions.go 6.4 KB

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