2023-10-17 14:17:37 +00:00
|
|
|
package cwhub
|
|
|
|
|
|
|
|
import (
|
2023-11-20 14:58:42 +00:00
|
|
|
"bytes"
|
2023-10-17 14:17:37 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-10-20 12:32:35 +00:00
|
|
|
"os"
|
2023-11-20 10:41:31 +00:00
|
|
|
"path"
|
2023-10-17 14:17:37 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
2023-10-19 10:04:29 +00:00
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
|
|
|
)
|
2023-10-17 14:17:37 +00:00
|
|
|
|
2023-10-19 10:04:29 +00:00
|
|
|
type Hub struct {
|
2023-11-20 10:41:31 +00:00
|
|
|
Items HubItems
|
|
|
|
local *csconfig.LocalHubCfg
|
|
|
|
remote *RemoteHubCfg
|
|
|
|
Warnings []string
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
|
|
|
|
2023-11-10 16:32:12 +00:00
|
|
|
func (h *Hub) GetDataDir() string {
|
|
|
|
return h.local.InstallDataDir
|
2023-10-19 10:04:29 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 11:47:39 +00:00
|
|
|
// NewHub returns a new Hub instance with local and (optionally) remote configuration, and syncs the local state
|
2023-11-20 14:58:42 +00:00
|
|
|
// It also downloads the index if updateIndex is true
|
|
|
|
func NewHub(local *csconfig.LocalHubCfg, remote *RemoteHubCfg, updateIndex bool) (*Hub, error) {
|
2023-10-30 16:23:50 +00:00
|
|
|
if local == nil {
|
|
|
|
return nil, fmt.Errorf("no hub configuration found")
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
|
|
|
|
2023-11-20 14:58:42 +00:00
|
|
|
hub := &Hub{
|
|
|
|
local: local,
|
|
|
|
remote: remote,
|
|
|
|
}
|
|
|
|
|
|
|
|
if updateIndex {
|
|
|
|
if err := hub.updateIndex(); err != nil {
|
2023-10-31 11:47:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-30 16:23:50 +00:00
|
|
|
log.Debugf("loading hub idx %s", local.HubIndexFile)
|
2023-10-20 12:32:35 +00:00
|
|
|
|
2023-11-10 16:32:12 +00:00
|
|
|
if err := hub.parseIndex(); err != nil {
|
2023-11-07 09:27:33 +00:00
|
|
|
return nil, fmt.Errorf("failed to load index: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-11-10 16:32:12 +00:00
|
|
|
if err := hub.localSync(); err != nil {
|
2023-11-08 12:21:59 +00:00
|
|
|
return nil, fmt.Errorf("failed to sync items: %w", err)
|
2023-10-20 12:32:35 +00:00
|
|
|
}
|
|
|
|
|
2023-11-10 16:32:12 +00:00
|
|
|
return hub, nil
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
|
|
|
|
2023-11-07 09:27:33 +00:00
|
|
|
// parseIndex takes the content of an index file and fills the map of associated parsers/scenarios/collections
|
|
|
|
func (h *Hub) parseIndex() error {
|
|
|
|
bidx, err := os.ReadFile(h.local.HubIndexFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to read index file: %w", err)
|
|
|
|
}
|
2023-10-17 14:17:37 +00:00
|
|
|
|
2023-11-07 09:27:33 +00:00
|
|
|
if err := json.Unmarshal(bidx, &h.Items); err != nil {
|
|
|
|
return fmt.Errorf("failed to unmarshal index: %w", err)
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("%d item types in hub index", len(ItemTypes))
|
|
|
|
|
|
|
|
// Iterate over the different types to complete the struct
|
|
|
|
for _, itemType := range ItemTypes {
|
2023-11-07 09:27:33 +00:00
|
|
|
log.Tracef("%s: %d items", itemType, len(h.Items[itemType]))
|
2023-10-17 14:17:37 +00:00
|
|
|
|
2023-11-07 09:27:33 +00:00
|
|
|
for name, item := range h.Items[itemType] {
|
2023-11-09 14:19:38 +00:00
|
|
|
item.hub = h
|
2023-10-17 14:17:37 +00:00
|
|
|
item.Name = name
|
2023-11-06 16:35:33 +00:00
|
|
|
|
|
|
|
// if the item has no (redundant) author, take it from the json key
|
|
|
|
if item.Author == "" && strings.Contains(name, "/") {
|
|
|
|
item.Author = strings.Split(name, "/")[0]
|
|
|
|
}
|
|
|
|
|
2023-10-17 14:17:37 +00:00
|
|
|
item.Type = itemType
|
2023-11-20 10:41:31 +00:00
|
|
|
item.FileName = path.Base(item.RemotePath)
|
2023-10-17 14:17:37 +00:00
|
|
|
|
2023-11-10 09:25:29 +00:00
|
|
|
item.logMissingSubItems()
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-07 09:27:33 +00:00
|
|
|
return nil
|
2023-10-17 14:17:37 +00:00
|
|
|
}
|
2023-10-20 12:32:35 +00:00
|
|
|
|
|
|
|
// ItemStats returns total counts of the hub items
|
2023-10-27 08:25:29 +00:00
|
|
|
func (h *Hub) ItemStats() []string {
|
2023-10-20 12:32:35 +00:00
|
|
|
loaded := ""
|
2023-11-20 10:41:31 +00:00
|
|
|
local := 0
|
|
|
|
tainted := 0
|
2023-10-30 16:23:50 +00:00
|
|
|
|
2023-10-20 12:32:35 +00:00
|
|
|
for _, itemType := range ItemTypes {
|
|
|
|
if len(h.Items[itemType]) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2023-10-30 16:23:50 +00:00
|
|
|
|
2023-10-20 12:32:35 +00:00
|
|
|
loaded += fmt.Sprintf("%d %s, ", len(h.Items[itemType]), itemType)
|
2023-11-20 10:41:31 +00:00
|
|
|
|
|
|
|
for _, item := range h.Items[itemType] {
|
|
|
|
if item.IsLocal() {
|
|
|
|
local++
|
|
|
|
}
|
|
|
|
|
|
|
|
if item.Tainted {
|
|
|
|
tainted++
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 12:32:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loaded = strings.Trim(loaded, ", ")
|
|
|
|
if loaded == "" {
|
|
|
|
loaded = "0 items"
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := []string{
|
|
|
|
fmt.Sprintf("Loaded: %s", loaded),
|
|
|
|
}
|
|
|
|
|
2023-11-20 10:41:31 +00:00
|
|
|
if local > 0 || tainted > 0 {
|
|
|
|
ret = append(ret, fmt.Sprintf("Unmanaged items: %d local, %d tainted", local, tainted))
|
2023-10-20 12:32:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
2023-11-20 14:58:42 +00:00
|
|
|
|
|
|
|
// updateIndex downloads the latest version of the index and writes it to disk if it changed
|
|
|
|
func (h *Hub) updateIndex() error {
|
|
|
|
body, err := h.remote.fetchIndex()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
oldContent, err := os.ReadFile(h.local.HubIndexFile)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
log.Warningf("failed to read hub index: %s", err)
|
|
|
|
}
|
|
|
|
} else if bytes.Equal(body, oldContent) {
|
|
|
|
log.Info("hub index is up to date")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = os.WriteFile(h.local.HubIndexFile, body, 0o644); err != nil {
|
|
|
|
return fmt.Errorf("failed to write hub index: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("Wrote index to %s, %d bytes", h.local.HubIndexFile, len(body))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|