github.com/crowdsecurity/crowdsec@v1.6.1/pkg/cwhub/itemupgrade.go (about) 1 package cwhub 2 3 // Install, upgrade and remove items from the hub to the local configuration 4 5 import ( 6 "bytes" 7 "crypto/sha256" 8 "encoding/hex" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "os" 14 "path/filepath" 15 16 "github.com/crowdsecurity/crowdsec/pkg/emoji" 17 ) 18 19 // Upgrade downloads and applies the last version of the item from the hub. 20 func (i *Item) Upgrade(force bool) (bool, error) { 21 updated := false 22 23 if i.State.IsLocal() { 24 i.hub.logger.Infof("not upgrading %s: local item", i.Name) 25 return false, nil 26 } 27 28 if !i.State.Downloaded { 29 return false, fmt.Errorf("can't upgrade %s: not installed", i.Name) 30 } 31 32 if !i.State.Installed { 33 return false, fmt.Errorf("can't upgrade %s: downloaded but not installed", i.Name) 34 } 35 36 if i.State.UpToDate { 37 i.hub.logger.Infof("%s: up-to-date", i.Name) 38 39 if err := i.DownloadDataIfNeeded(force); err != nil { 40 return false, fmt.Errorf("%s: download failed: %w", i.Name, err) 41 } 42 43 if !force { 44 // no upgrade needed 45 return false, nil 46 } 47 } 48 49 if _, err := i.downloadLatest(force, true); err != nil { 50 return false, fmt.Errorf("%s: download failed: %w", i.Name, err) 51 } 52 53 if !i.State.UpToDate { 54 if i.State.Tainted { 55 i.hub.logger.Warningf("%v %s is tainted, --force to overwrite", emoji.Warning, i.Name) 56 } 57 } else { 58 // a check on stdout is used while scripting to know if the hub has been upgraded 59 // and a configuration reload is required 60 // TODO: use a better way to communicate this 61 fmt.Printf("updated %s\n", i.Name) 62 i.hub.logger.Infof("%v %s: updated", emoji.Package, i.Name) 63 64 updated = true 65 } 66 67 return updated, nil 68 } 69 70 // downloadLatest downloads the latest version of the item to the hub directory. 71 func (i *Item) downloadLatest(overwrite bool, updateOnly bool) (string, error) { 72 i.hub.logger.Debugf("Downloading %s %s", i.Type, i.Name) 73 74 for _, sub := range i.SubItems() { 75 if !sub.State.Installed && updateOnly && sub.State.Downloaded { 76 i.hub.logger.Debugf("skipping upgrade of %s: not installed", i.Name) 77 continue 78 } 79 80 i.hub.logger.Debugf("Download %s sub-item: %s %s (%t -> %t)", i.Name, sub.Type, sub.Name, i.State.Installed, updateOnly) 81 82 // recurse as it's a collection 83 if sub.HasSubItems() { 84 i.hub.logger.Tracef("collection, recurse") 85 86 if _, err := sub.downloadLatest(overwrite, updateOnly); err != nil { 87 return "", err 88 } 89 } 90 91 downloaded := sub.State.Downloaded 92 93 if _, err := sub.download(overwrite); err != nil { 94 return "", err 95 } 96 97 // We need to enable an item when it has been added to a collection since latest release of the collection. 98 // We check if sub.Downloaded is false because maybe the item has been disabled by the user. 99 if !sub.State.Installed && !downloaded { 100 if err := sub.enable(); err != nil { 101 return "", fmt.Errorf("enabling '%s': %w", sub.Name, err) 102 } 103 } 104 } 105 106 if !i.State.Installed && updateOnly && i.State.Downloaded && !overwrite { 107 i.hub.logger.Debugf("skipping upgrade of %s: not installed", i.Name) 108 return "", nil 109 } 110 111 ret, err := i.download(overwrite) 112 if err != nil { 113 return "", err 114 } 115 116 return ret, nil 117 } 118 119 // FetchLatest downloads the latest item from the hub, verifies the hash and returns the content and the used url. 120 func (i *Item) FetchLatest() ([]byte, string, error) { 121 if i.latestHash() == "" { 122 return nil, "", errors.New("latest hash missing from index") 123 } 124 125 url, err := i.hub.remote.urlTo(i.RemotePath) 126 if err != nil { 127 return nil, "", fmt.Errorf("failed to build request: %w", err) 128 } 129 130 resp, err := hubClient.Get(url) 131 if err != nil { 132 return nil, "", err 133 } 134 defer resp.Body.Close() 135 136 if resp.StatusCode != http.StatusOK { 137 return nil, "", fmt.Errorf("bad http code %d", resp.StatusCode) 138 } 139 140 body, err := io.ReadAll(resp.Body) 141 if err != nil { 142 return nil, "", err 143 } 144 145 hash := sha256.New() 146 if _, err = hash.Write(body); err != nil { 147 return nil, "", fmt.Errorf("while hashing %s: %w", i.Name, err) 148 } 149 150 meow := hex.EncodeToString(hash.Sum(nil)) 151 if meow != i.Versions[i.Version].Digest { 152 i.hub.logger.Errorf("Downloaded version doesn't match index, please 'hub update'") 153 i.hub.logger.Debugf("got %s, expected %s", meow, i.Versions[i.Version].Digest) 154 155 return nil, "", errors.New("invalid download hash") 156 } 157 158 return body, url, nil 159 } 160 161 // download downloads the item from the hub and writes it to the hub directory. 162 func (i *Item) download(overwrite bool) (string, error) { 163 // ensure that target file is within target dir 164 finalPath, err := i.downloadPath() 165 if err != nil { 166 return "", err 167 } 168 169 if i.State.IsLocal() { 170 i.hub.logger.Warningf("%s is local, can't download", i.Name) 171 return finalPath, nil 172 } 173 174 // if user didn't --force, don't overwrite local, tainted, up-to-date files 175 if !overwrite { 176 if i.State.Tainted { 177 i.hub.logger.Debugf("%s: tainted, not updated", i.Name) 178 return "", nil 179 } 180 181 if i.State.UpToDate { 182 // We still have to check if data files are present 183 i.hub.logger.Debugf("%s: up-to-date, not updated", i.Name) 184 } 185 } 186 187 body, url, err := i.FetchLatest() 188 if err != nil { 189 what := i.Name 190 if url != "" { 191 what += " from " + url 192 } 193 194 return "", fmt.Errorf("while downloading %s: %w", what, err) 195 } 196 197 // all good, install 198 199 parentDir := filepath.Dir(finalPath) 200 201 if err = os.MkdirAll(parentDir, os.ModePerm); err != nil { 202 return "", fmt.Errorf("while creating %s: %w", parentDir, err) 203 } 204 205 // check actual file 206 if _, err = os.Stat(finalPath); !os.IsNotExist(err) { 207 i.hub.logger.Warningf("%s: overwrite", i.Name) 208 i.hub.logger.Debugf("target: %s", finalPath) 209 } else { 210 i.hub.logger.Infof("%s: OK", i.Name) 211 } 212 213 if err = os.WriteFile(finalPath, body, 0o644); err != nil { 214 return "", fmt.Errorf("while writing %s: %w", finalPath, err) 215 } 216 217 i.State.Downloaded = true 218 i.State.Tainted = false 219 i.State.UpToDate = true 220 221 if err = downloadDataSet(i.hub.local.InstallDataDir, overwrite, bytes.NewReader(body), i.hub.logger); err != nil { 222 return "", fmt.Errorf("while downloading data for %s: %w", i.FileName, err) 223 } 224 225 return finalPath, nil 226 } 227 228 // DownloadDataIfNeeded downloads the data set for the item. 229 func (i *Item) DownloadDataIfNeeded(force bool) error { 230 itemFilePath, err := i.installPath() 231 if err != nil { 232 return err 233 } 234 235 itemFile, err := os.Open(itemFilePath) 236 if err != nil { 237 return fmt.Errorf("while opening %s: %w", itemFilePath, err) 238 } 239 240 defer itemFile.Close() 241 242 if err = downloadDataSet(i.hub.local.InstallDataDir, force, itemFile, i.hub.logger); err != nil { 243 return fmt.Errorf("while downloading data for %s: %w", itemFilePath, err) 244 } 245 246 return nil 247 }