decisions.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. }
  58. func (c *Controller) DeleteDecisionById(gctx *gin.Context) {
  59. var err error
  60. decisionIDStr := gctx.Param("decision_id")
  61. decisionID, err := strconv.Atoi(decisionIDStr)
  62. if err != nil {
  63. gctx.JSON(http.StatusBadRequest, gin.H{"message": "decision_id must be valid integer"})
  64. return
  65. }
  66. err = c.DBClient.SoftDeleteDecisionByID(decisionID)
  67. if err != nil {
  68. c.HandleDBErrors(gctx, err)
  69. return
  70. }
  71. deleteDecisionResp := models.DeleteDecisionResponse{
  72. NbDeleted: "1",
  73. }
  74. gctx.JSON(http.StatusOK, deleteDecisionResp)
  75. return
  76. }
  77. func (c *Controller) DeleteDecisions(gctx *gin.Context) {
  78. var err error
  79. nbDeleted, err := c.DBClient.SoftDeleteDecisionsWithFilter(gctx.Request.URL.Query())
  80. if err != nil {
  81. c.HandleDBErrors(gctx, err)
  82. return
  83. }
  84. deleteDecisionResp := models.DeleteDecisionResponse{
  85. NbDeleted: nbDeleted,
  86. }
  87. gctx.JSON(http.StatusOK, deleteDecisionResp)
  88. return
  89. }
  90. func (c *Controller) StreamDecision(gctx *gin.Context) {
  91. var data []*ent.Decision
  92. ret := make(map[string][]*models.Decision, 0)
  93. ret["new"] = []*models.Decision{}
  94. ret["deleted"] = []*models.Decision{}
  95. val := gctx.Request.Header.Get(c.APIKeyHeader)
  96. hashedKey := sha512.New()
  97. hashedKey.Write([]byte(val))
  98. hashStr := fmt.Sprintf("%x", hashedKey.Sum(nil))
  99. bouncerInfo, err := c.DBClient.SelectBouncer(hashStr)
  100. if err != nil {
  101. if _, ok := err.(*ent.NotFoundError); ok {
  102. gctx.JSON(http.StatusForbidden, gin.H{"message": err.Error()})
  103. } else {
  104. gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
  105. }
  106. return
  107. }
  108. if bouncerInfo == nil {
  109. gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
  110. return
  111. }
  112. filters := make(map[string][]string)
  113. filters["scope"] = []string{"ip", "range"}
  114. if val, ok := gctx.Request.URL.Query()["scopes"]; ok {
  115. filters["scope"] = strings.Split(val[0], ",")
  116. }
  117. // if the blocker just start, return all decisions
  118. if val, ok := gctx.Request.URL.Query()["startup"]; ok {
  119. if val[0] == "true" {
  120. data, err := c.DBClient.QueryAllDecisionsWithFilters(filters)
  121. if err != nil {
  122. log.Errorf("failed querying decisions: %v", err)
  123. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  124. return
  125. }
  126. ret["new"], err = FormatDecisions(data)
  127. if err != nil {
  128. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  129. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  130. return
  131. }
  132. // getting expired decisions
  133. data, err = c.DBClient.QueryExpiredDecisionsWithFilters(filters)
  134. if err != nil {
  135. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  136. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  137. return
  138. }
  139. ret["deleted"], err = FormatDecisions(data)
  140. if err != nil {
  141. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  142. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  143. return
  144. }
  145. if err := c.DBClient.UpdateBouncerLastPull(time.Now().UTC(), bouncerInfo.ID); err != nil {
  146. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  147. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  148. return
  149. }
  150. if gctx.Request.Method == "HEAD" {
  151. gctx.String(http.StatusOK, "")
  152. return
  153. }
  154. gctx.JSON(http.StatusOK, ret)
  155. return
  156. }
  157. }
  158. // getting new decisions
  159. data, err = c.DBClient.QueryNewDecisionsSinceWithFilters(bouncerInfo.LastPull, filters)
  160. if err != nil {
  161. log.Errorf("unable to query new decision for '%s' : %v", bouncerInfo.Name, err)
  162. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  163. return
  164. }
  165. ret["new"], err = FormatDecisions(data)
  166. if err != nil {
  167. log.Errorf("unable to format new decision for '%s' : %v", bouncerInfo.Name, err)
  168. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  169. return
  170. }
  171. // getting expired decisions
  172. data, err = c.DBClient.QueryExpiredDecisionsSinceWithFilters(bouncerInfo.LastPull.Add((-2 * time.Second)), filters) // do we want to give exactly lastPull time ?
  173. if err != nil {
  174. log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
  175. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  176. return
  177. }
  178. ret["deleted"], err = FormatDecisions(data)
  179. if err != nil {
  180. log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
  181. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  182. return
  183. }
  184. if err := c.DBClient.UpdateBouncerLastPull(time.Now().UTC(), bouncerInfo.ID); err != nil {
  185. log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
  186. gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
  187. return
  188. }
  189. gctx.JSON(http.StatusOK, ret)
  190. return
  191. }