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 }