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  }