decisions_service.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. package apiclient
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "net/http"
  7. qs "github.com/google/go-querystring/query"
  8. "github.com/pkg/errors"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/crowdsecurity/go-cs-lib/pkg/ptr"
  11. "github.com/crowdsecurity/crowdsec/pkg/models"
  12. "github.com/crowdsecurity/crowdsec/pkg/modelscapi"
  13. "github.com/crowdsecurity/crowdsec/pkg/types"
  14. )
  15. type DecisionsService service
  16. type DecisionsListOpts struct {
  17. ScopeEquals *string `url:"scope,omitempty"`
  18. ValueEquals *string `url:"value,omitempty"`
  19. TypeEquals *string `url:"type,omitempty"`
  20. IPEquals *string `url:"ip,omitempty"`
  21. RangeEquals *string `url:"range,omitempty"`
  22. Contains *bool `url:"contains,omitempty"`
  23. ListOpts
  24. }
  25. type DecisionsStreamOpts struct {
  26. Startup bool `url:"startup,omitempty"`
  27. Scopes string `url:"scopes,omitempty"`
  28. ScenariosContaining string `url:"scenarios_containing,omitempty"`
  29. ScenariosNotContaining string `url:"scenarios_not_containing,omitempty"`
  30. Origins string `url:"origins,omitempty"`
  31. }
  32. func (o *DecisionsStreamOpts) addQueryParamsToURL(url string) (string, error) {
  33. params, err := qs.Values(o)
  34. if err != nil {
  35. return "", err
  36. }
  37. return fmt.Sprintf("%s?%s", url, params.Encode()), nil
  38. }
  39. type DecisionsDeleteOpts struct {
  40. ScopeEquals *string `url:"scope,omitempty"`
  41. ValueEquals *string `url:"value,omitempty"`
  42. TypeEquals *string `url:"type,omitempty"`
  43. IPEquals *string `url:"ip,omitempty"`
  44. RangeEquals *string `url:"range,omitempty"`
  45. Contains *bool `url:"contains,omitempty"`
  46. OriginEquals *string `url:"origin,omitempty"`
  47. //
  48. ScenarioEquals *string `url:"scenario,omitempty"`
  49. ListOpts
  50. }
  51. // to demo query arguments
  52. func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*models.GetDecisionsResponse, *Response, error) {
  53. var decisions models.GetDecisionsResponse
  54. params, err := qs.Values(opts)
  55. if err != nil {
  56. return nil, nil, err
  57. }
  58. u := fmt.Sprintf("%s/decisions?%s", s.client.URLPrefix, params.Encode())
  59. req, err := s.client.NewRequest(http.MethodGet, u, nil)
  60. if err != nil {
  61. return nil, nil, err
  62. }
  63. resp, err := s.client.Do(ctx, req, &decisions)
  64. if err != nil {
  65. return nil, resp, err
  66. }
  67. return &decisions, resp, nil
  68. }
  69. func (s *DecisionsService) FetchV2Decisions(ctx context.Context, url string) (*models.DecisionsStreamResponse, *Response, error) {
  70. var decisions models.DecisionsStreamResponse
  71. req, err := s.client.NewRequest(http.MethodGet, url, nil)
  72. if err != nil {
  73. return nil, nil, err
  74. }
  75. resp, err := s.client.Do(ctx, req, &decisions)
  76. if err != nil {
  77. return nil, resp, err
  78. }
  79. return &decisions, resp, nil
  80. }
  81. func (s *DecisionsService) GetDecisionsFromGroups(decisionsGroups []*modelscapi.GetDecisionsStreamResponseNewItem) []*models.Decision {
  82. var decisions []*models.Decision
  83. for _, decisionsGroup := range decisionsGroups {
  84. partialDecisions := make([]*models.Decision, len(decisionsGroup.Decisions))
  85. for idx, decision := range decisionsGroup.Decisions {
  86. partialDecisions[idx] = &models.Decision{
  87. Scenario: decisionsGroup.Scenario,
  88. Scope: decisionsGroup.Scope,
  89. Type: ptr.Of(types.DecisionTypeBan),
  90. Value: decision.Value,
  91. Duration: decision.Duration,
  92. Origin: ptr.Of(types.CAPIOrigin),
  93. }
  94. }
  95. decisions = append(decisions, partialDecisions...)
  96. }
  97. return decisions
  98. }
  99. func (s *DecisionsService) FetchV3Decisions(ctx context.Context, url string) (*models.DecisionsStreamResponse, *Response, error) {
  100. var decisions modelscapi.GetDecisionsStreamResponse
  101. var v2Decisions models.DecisionsStreamResponse
  102. scenarioDeleted := "deleted"
  103. durationDeleted := "1h"
  104. req, err := s.client.NewRequest(http.MethodGet, url, nil)
  105. if err != nil {
  106. return nil, nil, err
  107. }
  108. resp, err := s.client.Do(ctx, req, &decisions)
  109. if err != nil {
  110. return nil, resp, err
  111. }
  112. v2Decisions.New = s.GetDecisionsFromGroups(decisions.New)
  113. for _, decisionsGroup := range decisions.Deleted {
  114. partialDecisions := make([]*models.Decision, len(decisionsGroup.Decisions))
  115. for idx, decision := range decisionsGroup.Decisions {
  116. decision := decision // fix exportloopref linter message
  117. partialDecisions[idx] = &models.Decision{
  118. Scenario: &scenarioDeleted,
  119. Scope: decisionsGroup.Scope,
  120. Type: ptr.Of(types.DecisionTypeBan),
  121. Value: &decision,
  122. Duration: &durationDeleted,
  123. Origin: ptr.Of(types.CAPIOrigin),
  124. }
  125. }
  126. v2Decisions.Deleted = append(v2Decisions.Deleted, partialDecisions...)
  127. }
  128. return &v2Decisions, resp, nil
  129. }
  130. func (s *DecisionsService) GetDecisionsFromBlocklist(ctx context.Context, blocklist *modelscapi.BlocklistLink, lastPullTimestamp *string) ([]*models.Decision, bool, error) {
  131. if blocklist.URL == nil {
  132. return nil, false, errors.New("blocklist URL is nil")
  133. }
  134. log.Debugf("Fetching blocklist %s", *blocklist.URL)
  135. client := http.Client{}
  136. req, err := http.NewRequest(http.MethodGet, *blocklist.URL, nil)
  137. if err != nil {
  138. return nil, false, err
  139. }
  140. if lastPullTimestamp != nil {
  141. req.Header.Set("If-Modified-Since", *lastPullTimestamp)
  142. }
  143. req = req.WithContext(ctx)
  144. log.Debugf("[URL] %s %s", req.Method, req.URL)
  145. // we dont use client_http Do method because we need the reader and is not provided. We would be forced to use Pipe and goroutine, etc
  146. resp, err := client.Do(req)
  147. if resp != nil && resp.Body != nil {
  148. defer resp.Body.Close()
  149. }
  150. if err != nil {
  151. // If we got an error, and the context has been canceled,
  152. // the context's error is probably more useful.
  153. select {
  154. case <-ctx.Done():
  155. return nil, false, ctx.Err()
  156. default:
  157. }
  158. // If the error type is *url.Error, sanitize its URL before returning.
  159. log.Errorf("Error fetching blocklist %s: %s", *blocklist.URL, err)
  160. return nil, false, err
  161. }
  162. if resp.StatusCode == http.StatusNotModified {
  163. if lastPullTimestamp != nil {
  164. log.Debugf("Blocklist %s has not been modified since %s", *blocklist.URL, *lastPullTimestamp)
  165. } else {
  166. log.Debugf("Blocklist %s has not been modified (decisions about to expire)", *blocklist.URL)
  167. }
  168. return nil, false, nil
  169. }
  170. if resp.StatusCode != http.StatusOK {
  171. log.Debugf("Received nok status code %d for blocklist %s", resp.StatusCode, *blocklist.URL)
  172. return nil, false, nil
  173. }
  174. decisions := make([]*models.Decision, 0)
  175. scanner := bufio.NewScanner(resp.Body)
  176. for scanner.Scan() {
  177. decision := scanner.Text()
  178. decisions = append(decisions, &models.Decision{
  179. Scenario: blocklist.Name,
  180. Scope: blocklist.Scope,
  181. Type: blocklist.Remediation,
  182. Value: &decision,
  183. Duration: blocklist.Duration,
  184. Origin: ptr.Of(types.ListOrigin),
  185. })
  186. }
  187. // here the upper go routine is finished because scanner.Scan() is blocking until pw.Close() is called
  188. // so it's safe to use the isModified variable here
  189. return decisions, true, nil
  190. }
  191. func (s *DecisionsService) GetStream(ctx context.Context, opts DecisionsStreamOpts) (*models.DecisionsStreamResponse, *Response, error) {
  192. u, err := opts.addQueryParamsToURL(s.client.URLPrefix + "/decisions/stream")
  193. if err != nil {
  194. return nil, nil, err
  195. }
  196. if s.client.URLPrefix == "v3" {
  197. return s.FetchV3Decisions(ctx, u)
  198. } else {
  199. return s.FetchV2Decisions(ctx, u)
  200. }
  201. }
  202. func (s *DecisionsService) GetStreamV3(ctx context.Context, opts DecisionsStreamOpts) (*modelscapi.GetDecisionsStreamResponse, *Response, error) {
  203. u, err := opts.addQueryParamsToURL(s.client.URLPrefix + "/decisions/stream")
  204. if err != nil {
  205. return nil, nil, err
  206. }
  207. var decisions modelscapi.GetDecisionsStreamResponse
  208. req, err := s.client.NewRequest(http.MethodGet, u, nil)
  209. if err != nil {
  210. return nil, nil, err
  211. }
  212. resp, err := s.client.Do(ctx, req, &decisions)
  213. if err != nil {
  214. return nil, resp, err
  215. }
  216. return &decisions, resp, nil
  217. }
  218. func (s *DecisionsService) StopStream(ctx context.Context) (*Response, error) {
  219. u := fmt.Sprintf("%s/decisions", s.client.URLPrefix)
  220. req, err := s.client.NewRequest(http.MethodDelete, u, nil)
  221. if err != nil {
  222. return nil, err
  223. }
  224. resp, err := s.client.Do(ctx, req, nil)
  225. if err != nil {
  226. return resp, err
  227. }
  228. return resp, nil
  229. }
  230. func (s *DecisionsService) Delete(ctx context.Context, opts DecisionsDeleteOpts) (*models.DeleteDecisionResponse, *Response, error) {
  231. var deleteDecisionResponse models.DeleteDecisionResponse
  232. params, err := qs.Values(opts)
  233. if err != nil {
  234. return nil, nil, err
  235. }
  236. u := fmt.Sprintf("%s/decisions?%s", s.client.URLPrefix, params.Encode())
  237. req, err := s.client.NewRequest(http.MethodDelete, u, nil)
  238. if err != nil {
  239. return nil, nil, err
  240. }
  241. resp, err := s.client.Do(ctx, req, &deleteDecisionResponse)
  242. if err != nil {
  243. return nil, resp, err
  244. }
  245. return &deleteDecisionResponse, resp, nil
  246. }
  247. func (s *DecisionsService) DeleteOne(ctx context.Context, decision_id string) (*models.DeleteDecisionResponse, *Response, error) {
  248. var deleteDecisionResponse models.DeleteDecisionResponse
  249. u := fmt.Sprintf("%s/decisions/%s", s.client.URLPrefix, decision_id)
  250. req, err := s.client.NewRequest(http.MethodDelete, u, nil)
  251. if err != nil {
  252. return nil, nil, err
  253. }
  254. resp, err := s.client.Do(ctx, req, &deleteDecisionResponse)
  255. if err != nil {
  256. return nil, resp, err
  257. }
  258. return &deleteDecisionResponse, resp, nil
  259. }