github.com/crowdsecurity/crowdsec@v1.6.1/pkg/alertcontext/config.go (about)

     1  package alertcontext
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"os"
     9  	"path/filepath"
    10  	"slices"
    11  
    12  	log "github.com/sirupsen/logrus"
    13  	"gopkg.in/yaml.v3"
    14  
    15  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    16  	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
    17  )
    18  
    19  var ErrNoContextData = errors.New("no context to send")
    20  
    21  // this file is here to avoid circular dependencies between the configuration and the hub
    22  
    23  // HubItemWrapper is a wrapper around a hub item to unmarshal only the context part
    24  // because there are other fields like name etc.
    25  type HubItemWrapper struct {
    26  	Context map[string][]string `yaml:"context"`
    27  }
    28  
    29  // mergeContext adds the context from src to dest.
    30  func mergeContext(dest map[string][]string, src map[string][]string) error {
    31  	if len(src) == 0 {
    32  		return ErrNoContextData
    33  	}
    34  
    35  	for k, v := range src {
    36  		if _, ok := dest[k]; !ok {
    37  			dest[k] = make([]string, 0)
    38  		}
    39  
    40  		for _, s := range v {
    41  			if !slices.Contains(dest[k], s) {
    42  				dest[k] = append(dest[k], s)
    43  			}
    44  		}
    45  	}
    46  
    47  	return nil
    48  }
    49  
    50  // addContextFromItem merges the context from an item into the context to send to the console.
    51  func addContextFromItem(toSend map[string][]string, item *cwhub.Item) error {
    52  	filePath := item.State.LocalPath
    53  	log.Tracef("loading console context from %s", filePath)
    54  
    55  	content, err := os.ReadFile(filePath)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	wrapper := &HubItemWrapper{}
    61  
    62  	err = yaml.Unmarshal(content, wrapper)
    63  	if err != nil {
    64  		return fmt.Errorf("%s: %w", filePath, err)
    65  	}
    66  
    67  	err = mergeContext(toSend, wrapper.Context)
    68  	if err != nil {
    69  		// having an empty hub item deserves an error
    70  		log.Errorf("while merging context from %s: %s. Note that context data should be under the 'context:' key, the top-level is metadata.", filePath, err)
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  // addContextFromFile merges the context from a file into the context to send to the console.
    77  func addContextFromFile(toSend map[string][]string, filePath string) error {
    78  	log.Tracef("loading console context from %s", filePath)
    79  
    80  	content, err := os.ReadFile(filePath)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	newContext := make(map[string][]string, 0)
    86  
    87  	err = yaml.Unmarshal(content, newContext)
    88  	if err != nil {
    89  		return fmt.Errorf("%s: %w", filePath, err)
    90  	}
    91  
    92  	err = mergeContext(toSend, newContext)
    93  	if err != nil && !errors.Is(err, ErrNoContextData) {
    94  		// having an empty console/context.yaml is not an error
    95  		return err
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  
   102  // LoadConsoleContext loads the context from the hub (if provided) and the file console_context_path.
   103  func LoadConsoleContext(c *csconfig.Config, hub *cwhub.Hub) error {
   104  	c.Crowdsec.ContextToSend = make(map[string][]string, 0)
   105  
   106  	if hub != nil {
   107  		items, err := hub.GetInstalledItems(cwhub.CONTEXTS)
   108  		if err != nil {
   109  			return err
   110  		}
   111  
   112  		for _, item := range items {
   113  			// context in item files goes under the key 'context'
   114  			if err = addContextFromItem(c.Crowdsec.ContextToSend, item); err != nil {
   115  				return err
   116  			}
   117  		}
   118  	}
   119  
   120  	ignoreMissing := false
   121  
   122  	if c.Crowdsec.ConsoleContextPath != "" {
   123  		// if it's provided, it must exist
   124  		if _, err := os.Stat(c.Crowdsec.ConsoleContextPath); err != nil {
   125  			return fmt.Errorf("while checking console_context_path: %w", err)
   126  		}
   127  	} else {
   128  		c.Crowdsec.ConsoleContextPath = filepath.Join(c.ConfigPaths.ConfigDir, "console", "context.yaml")
   129  		ignoreMissing = true
   130  	}
   131  
   132  	if err := addContextFromFile(c.Crowdsec.ContextToSend, c.Crowdsec.ConsoleContextPath); err != nil {
   133  		if !errors.Is(err, fs.ErrNotExist) {
   134  			return err
   135  		} else if !ignoreMissing {
   136  			log.Warningf("while merging context from %s: %s", c.Crowdsec.ConsoleContextPath, err)
   137  		}
   138  	}
   139  
   140  	feedback, err := json.Marshal(c.Crowdsec.ContextToSend)
   141  	if err != nil {
   142  		return fmt.Errorf("marshaling console context: %s", err)
   143  	}
   144  
   145  	log.Debugf("console context to send: %s", feedback)
   146  
   147  	return nil
   148  }