hub.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package cwhub
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "strings"
  11. log "github.com/sirupsen/logrus"
  12. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  13. )
  14. const HubIndexFile = ".index.json"
  15. // Hub represents the runtime status of the hub (parsed items, etc.)
  16. type Hub struct {
  17. Items HubItems
  18. cfg *csconfig.HubCfg
  19. skippedLocal int
  20. skippedTainted int
  21. }
  22. var (
  23. theHub *Hub
  24. ErrIndexNotFound = fmt.Errorf("index not found")
  25. )
  26. // GetHub returns the hub singleton
  27. // it returns an error if it's not initialized to avoid nil dereference
  28. func GetHub() (*Hub, error) {
  29. if theHub == nil {
  30. return nil, fmt.Errorf("hub not initialized")
  31. }
  32. return theHub, nil
  33. }
  34. // InitHub initializes the Hub, syncs the local state and returns the singleton for immediate use
  35. func InitHub(cfg *csconfig.HubCfg) (*Hub, error) {
  36. if cfg == nil {
  37. return nil, fmt.Errorf("no configuration found for hub")
  38. }
  39. log.Debugf("loading hub idx %s", cfg.HubIndexFile)
  40. bidx, err := os.ReadFile(cfg.HubIndexFile)
  41. if err != nil {
  42. return nil, fmt.Errorf("unable to read index file: %w", err)
  43. }
  44. ret, err := ParseIndex(bidx)
  45. if err != nil {
  46. if !errors.Is(err, ErrMissingReference) {
  47. return nil, fmt.Errorf("unable to load existing index: %w", err)
  48. }
  49. // XXX: why the error check if we bail out anyway?
  50. return nil, err
  51. }
  52. theHub = &Hub{
  53. Items: ret,
  54. cfg: cfg,
  55. }
  56. _, err = theHub.LocalSync()
  57. if err != nil {
  58. return nil, fmt.Errorf("failed to sync Hub index with local deployment : %w", err)
  59. }
  60. return theHub, nil
  61. }
  62. // InitHubUpdate is like InitHub but downloads and updates the index instead of reading from the disk
  63. // It is used to inizialize the hub when there is no index file yet
  64. func InitHubUpdate(cfg *csconfig.HubCfg) (*Hub, error) {
  65. if cfg == nil {
  66. return nil, fmt.Errorf("no configuration found for hub")
  67. }
  68. bidx, err := DownloadIndex(cfg.HubIndexFile)
  69. if err != nil {
  70. return nil, fmt.Errorf("failed to download index: %w", err)
  71. }
  72. ret, err := ParseIndex(bidx)
  73. if err != nil {
  74. if !errors.Is(err, ErrMissingReference) {
  75. return nil, fmt.Errorf("failed to read index: %w", err)
  76. }
  77. }
  78. theHub = &Hub{
  79. Items: ret,
  80. cfg: cfg,
  81. }
  82. if _, err := theHub.LocalSync(); err != nil {
  83. return nil, fmt.Errorf("failed to sync: %w", err)
  84. }
  85. return theHub, nil
  86. }
  87. // DownloadIndex downloads the latest version of the index and returns the content
  88. func DownloadIndex(indexPath string) ([]byte, error) {
  89. log.Debugf("fetching index from branch %s (%s)", HubBranch, fmt.Sprintf(RawFileURLTemplate, HubBranch, HubIndexFile))
  90. req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(RawFileURLTemplate, HubBranch, HubIndexFile), nil)
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to build request for hub index: %w", err)
  93. }
  94. resp, err := http.DefaultClient.Do(req)
  95. if err != nil {
  96. return nil, fmt.Errorf("failed http request for hub index: %w", err)
  97. }
  98. defer resp.Body.Close()
  99. if resp.StatusCode != http.StatusOK {
  100. if resp.StatusCode == http.StatusNotFound {
  101. return nil, ErrIndexNotFound
  102. }
  103. return nil, fmt.Errorf("bad http code %d while requesting %s", resp.StatusCode, req.URL.String())
  104. }
  105. body, err := io.ReadAll(resp.Body)
  106. if err != nil {
  107. return nil, fmt.Errorf("failed to read request answer for hub index: %w", err)
  108. }
  109. oldContent, err := os.ReadFile(indexPath)
  110. if err != nil {
  111. if !os.IsNotExist(err) {
  112. log.Warningf("failed to read hub index: %s", err)
  113. }
  114. } else if bytes.Equal(body, oldContent) {
  115. log.Info("hub index is up to date")
  116. // write it anyway, can't hurt
  117. }
  118. file, err := os.OpenFile(indexPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
  119. if err != nil {
  120. return nil, fmt.Errorf("while opening hub index file: %w", err)
  121. }
  122. defer file.Close()
  123. wsize, err := file.Write(body)
  124. if err != nil {
  125. return nil, fmt.Errorf("while writing hub index file: %w", err)
  126. }
  127. log.Infof("Wrote new %d bytes index to %s", wsize, indexPath)
  128. return body, nil
  129. }
  130. // ParseIndex takes the content of an index file and returns the map of associated parsers/scenarios/collections
  131. func ParseIndex(buff []byte) (HubItems, error) {
  132. var (
  133. RawIndex HubItems
  134. missingItems []string
  135. )
  136. if err := json.Unmarshal(buff, &RawIndex); err != nil {
  137. return nil, fmt.Errorf("failed to unmarshal index: %w", err)
  138. }
  139. log.Debugf("%d item types in hub index", len(ItemTypes))
  140. // Iterate over the different types to complete the struct
  141. for _, itemType := range ItemTypes {
  142. log.Tracef("%s: %d items", itemType, len(RawIndex[itemType]))
  143. for name, item := range RawIndex[itemType] {
  144. item.Name = name
  145. item.Type = itemType
  146. x := strings.Split(item.RemotePath, "/")
  147. item.FileName = x[len(x)-1]
  148. RawIndex[itemType][name] = item
  149. if itemType != COLLECTIONS {
  150. continue
  151. }
  152. // if it's a collection, check its sub-items are present
  153. // XXX should be done later
  154. for _, sub := range item.SubItems() {
  155. if _, ok := RawIndex[sub.Type][sub.Name]; !ok {
  156. log.Errorf("Referred %s %s in collection %s doesn't exist.", sub.Type, sub.Name, item.Name)
  157. missingItems = append(missingItems, sub.Name)
  158. }
  159. }
  160. }
  161. }
  162. if len(missingItems) > 0 {
  163. return RawIndex, fmt.Errorf("%q: %w", missingItems, ErrMissingReference)
  164. }
  165. return RawIndex, nil
  166. }
  167. // ItemStats returns total counts of the hub items
  168. func (h Hub) ItemStats() []string {
  169. loaded := ""
  170. for _, itemType := range ItemTypes {
  171. // ensure the order is always the same
  172. if h.Items[itemType] == nil {
  173. continue
  174. }
  175. if len(h.Items[itemType]) == 0 {
  176. continue
  177. }
  178. loaded += fmt.Sprintf("%d %s, ", len(h.Items[itemType]), itemType)
  179. }
  180. loaded = strings.Trim(loaded, ", ")
  181. if loaded == "" {
  182. // empty hub
  183. loaded = "0 items"
  184. }
  185. ret := []string{
  186. fmt.Sprintf("Loaded: %s", loaded),
  187. }
  188. if h.skippedLocal > 0 || h.skippedTainted > 0 {
  189. ret = append(ret, fmt.Sprintf("Unmanaged items: %d local, %d tainted", h.skippedLocal, h.skippedTainted))
  190. }
  191. return ret
  192. }