github.com/crowdsecurity/crowdsec@v1.6.1/pkg/cwhub/hub.go (about)

     1  package cwhub
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path"
    11  	"slices"
    12  	"strings"
    13  
    14  	"github.com/sirupsen/logrus"
    15  
    16  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    17  )
    18  
    19  // Hub is the main structure for the package.
    20  type Hub struct {
    21  	items    HubItems // Items read from HubDir and InstallDir
    22  	local    *csconfig.LocalHubCfg
    23  	remote   *RemoteHubCfg
    24  	Warnings []string // Warnings encountered during sync
    25  	logger   *logrus.Logger
    26  }
    27  
    28  // GetDataDir returns the data directory, where data sets are installed.
    29  func (h *Hub) GetDataDir() string {
    30  	return h.local.InstallDataDir
    31  }
    32  
    33  // NewHub returns a new Hub instance with local and (optionally) remote configuration, and syncs the local state.
    34  // If updateIndex is true, the local index file is updated from the remote before reading the state of the items.
    35  // All download operations (including updateIndex) return ErrNilRemoteHub if the remote configuration is not set.
    36  func NewHub(local *csconfig.LocalHubCfg, remote *RemoteHubCfg, updateIndex bool, logger *logrus.Logger) (*Hub, error) {
    37  	if local == nil {
    38  		return nil, errors.New("no hub configuration found")
    39  	}
    40  
    41  	if logger == nil {
    42  		logger = logrus.New()
    43  		logger.SetOutput(io.Discard)
    44  	}
    45  
    46  	hub := &Hub{
    47  		local:  local,
    48  		remote: remote,
    49  		logger: logger,
    50  	}
    51  
    52  	if updateIndex {
    53  		if err := hub.updateIndex(); err != nil {
    54  			return nil, err
    55  		}
    56  	}
    57  
    58  	logger.Debugf("loading hub idx %s", local.HubIndexFile)
    59  
    60  	if err := hub.parseIndex(); err != nil {
    61  		return nil, fmt.Errorf("failed to load index: %w", err)
    62  	}
    63  
    64  	if err := hub.localSync(); err != nil {
    65  		return nil, fmt.Errorf("failed to sync items: %w", err)
    66  	}
    67  
    68  	return hub, nil
    69  }
    70  
    71  // parseIndex takes the content of an index file and fills the map of associated parsers/scenarios/collections.
    72  func (h *Hub) parseIndex() error {
    73  	bidx, err := os.ReadFile(h.local.HubIndexFile)
    74  	if err != nil {
    75  		return fmt.Errorf("unable to read index file: %w", err)
    76  	}
    77  
    78  	if err := json.Unmarshal(bidx, &h.items); err != nil {
    79  		return fmt.Errorf("failed to unmarshal index: %w", err)
    80  	}
    81  
    82  	h.logger.Debugf("%d item types in hub index", len(ItemTypes))
    83  
    84  	// Iterate over the different types to complete the struct
    85  	for _, itemType := range ItemTypes {
    86  		h.logger.Tracef("%s: %d items", itemType, len(h.GetItemMap(itemType)))
    87  
    88  		for name, item := range h.GetItemMap(itemType) {
    89  			item.hub = h
    90  			item.Name = name
    91  
    92  			// if the item has no (redundant) author, take it from the json key
    93  			if item.Author == "" && strings.Contains(name, "/") {
    94  				item.Author = strings.Split(name, "/")[0]
    95  			}
    96  
    97  			item.Type = itemType
    98  			item.FileName = path.Base(item.RemotePath)
    99  
   100  			item.logMissingSubItems()
   101  
   102  			if item.latestHash() == "" {
   103  				h.logger.Errorf("invalid hub item %s: latest version missing from index", item.FQName())
   104  			}
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // ItemStats returns total counts of the hub items, including local and tainted.
   112  func (h *Hub) ItemStats() []string {
   113  	loaded := ""
   114  	local := 0
   115  	tainted := 0
   116  
   117  	for _, itemType := range ItemTypes {
   118  		if len(h.GetItemMap(itemType)) == 0 {
   119  			continue
   120  		}
   121  
   122  		loaded += fmt.Sprintf("%d %s, ", len(h.GetItemMap(itemType)), itemType)
   123  
   124  		for _, item := range h.GetItemMap(itemType) {
   125  			if item.State.IsLocal() {
   126  				local++
   127  			}
   128  
   129  			if item.State.Tainted {
   130  				tainted++
   131  			}
   132  		}
   133  	}
   134  
   135  	loaded = strings.Trim(loaded, ", ")
   136  	if loaded == "" {
   137  		loaded = "0 items"
   138  	}
   139  
   140  	ret := []string{
   141  		fmt.Sprintf("Loaded: %s", loaded),
   142  	}
   143  
   144  	if local > 0 || tainted > 0 {
   145  		ret = append(ret, fmt.Sprintf("Unmanaged items: %d local, %d tainted", local, tainted))
   146  	}
   147  
   148  	return ret
   149  }
   150  
   151  // updateIndex downloads the latest version of the index and writes it to disk if it changed.
   152  func (h *Hub) updateIndex() error {
   153  	body, err := h.remote.fetchIndex()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	oldContent, err := os.ReadFile(h.local.HubIndexFile)
   159  	if err != nil {
   160  		if !os.IsNotExist(err) {
   161  			h.logger.Warningf("failed to read hub index: %s", err)
   162  		}
   163  	} else if bytes.Equal(body, oldContent) {
   164  		h.logger.Info("hub index is up to date")
   165  		return nil
   166  	}
   167  
   168  	if err = os.WriteFile(h.local.HubIndexFile, body, 0o644); err != nil {
   169  		return fmt.Errorf("failed to write hub index: %w", err)
   170  	}
   171  
   172  	h.logger.Infof("Wrote index to %s, %d bytes", h.local.HubIndexFile, len(body))
   173  
   174  	return nil
   175  }
   176  
   177  func (h *Hub) addItem(item *Item) {
   178  	if h.items[item.Type] == nil {
   179  		h.items[item.Type] = make(map[string]*Item)
   180  	}
   181  
   182  	h.items[item.Type][item.Name] = item
   183  }
   184  
   185  // GetItemMap returns the map of items for a given type.
   186  func (h *Hub) GetItemMap(itemType string) map[string]*Item {
   187  	return h.items[itemType]
   188  }
   189  
   190  // GetItem returns an item from hub based on its type and full name (author/name).
   191  func (h *Hub) GetItem(itemType string, itemName string) *Item {
   192  	return h.GetItemMap(itemType)[itemName]
   193  }
   194  
   195  // GetItemFQ returns an item from hub based on its type and name (type:author/name).
   196  func (h *Hub) GetItemFQ(itemFQName string) (*Item, error) {
   197  	// type and name are separated by a colon
   198  	parts := strings.Split(itemFQName, ":")
   199  
   200  	if len(parts) != 2 {
   201  		return nil, fmt.Errorf("invalid item name %s", itemFQName)
   202  	}
   203  
   204  	m := h.GetItemMap(parts[0])
   205  	if m == nil {
   206  		return nil, fmt.Errorf("invalid item type %s", parts[0])
   207  	}
   208  
   209  	i := m[parts[1]]
   210  	if i == nil {
   211  		return nil, fmt.Errorf("item %s not found", parts[1])
   212  	}
   213  
   214  	return i, nil
   215  }
   216  
   217  // GetItemNames returns a slice of (full) item names for a given type
   218  // (eg. for collections: crowdsecurity/apache2 crowdsecurity/nginx).
   219  func (h *Hub) GetItemNames(itemType string) []string {
   220  	m := h.GetItemMap(itemType)
   221  	if m == nil {
   222  		return nil
   223  	}
   224  
   225  	names := make([]string, 0, len(m))
   226  	for k := range m {
   227  		names = append(names, k)
   228  	}
   229  
   230  	return names
   231  }
   232  
   233  // GetAllItems returns a slice of all the items of a given type, installed or not.
   234  func (h *Hub) GetAllItems(itemType string) ([]*Item, error) {
   235  	if !slices.Contains(ItemTypes, itemType) {
   236  		return nil, fmt.Errorf("invalid item type %s", itemType)
   237  	}
   238  
   239  	items := h.items[itemType]
   240  
   241  	ret := make([]*Item, len(items))
   242  
   243  	idx := 0
   244  
   245  	for _, item := range items {
   246  		ret[idx] = item
   247  		idx++
   248  	}
   249  
   250  	return ret, nil
   251  }
   252  
   253  // GetInstalledItems returns a slice of the installed items of a given type.
   254  func (h *Hub) GetInstalledItems(itemType string) ([]*Item, error) {
   255  	if !slices.Contains(ItemTypes, itemType) {
   256  		return nil, fmt.Errorf("invalid item type %s", itemType)
   257  	}
   258  
   259  	items := h.items[itemType]
   260  
   261  	retItems := make([]*Item, 0)
   262  
   263  	for _, item := range items {
   264  		if item.State.Installed {
   265  			retItems = append(retItems, item)
   266  		}
   267  	}
   268  
   269  	return retItems, nil
   270  }
   271  
   272  // GetInstalledItemNames returns the names of the installed items of a given type.
   273  func (h *Hub) GetInstalledItemNames(itemType string) ([]string, error) {
   274  	items, err := h.GetInstalledItems(itemType)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	retStr := make([]string, len(items))
   280  
   281  	for idx, it := range items {
   282  		retStr[idx] = it.Name
   283  	}
   284  
   285  	return retStr, nil
   286  }