github.com/crowdsecurity/crowdsec@v1.6.1/pkg/setup/install.go (about)

     1  package setup
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	goccyyaml "github.com/goccy/go-yaml"
    11  	"gopkg.in/yaml.v3"
    12  
    13  	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
    14  )
    15  
    16  // AcquisDocument is created from a SetupItem. It represents a single YAML document, and can be part of a multi-document file.
    17  type AcquisDocument struct {
    18  	AcquisFilename string
    19  	DataSource     map[string]interface{}
    20  }
    21  
    22  func decodeSetup(input []byte, fancyErrors bool) (Setup, error) {
    23  	ret := Setup{}
    24  
    25  	// parse with goccy to have better error messages in many cases
    26  	dec := goccyyaml.NewDecoder(bytes.NewBuffer(input), goccyyaml.Strict())
    27  
    28  	if err := dec.Decode(&ret); err != nil {
    29  		if fancyErrors {
    30  			return ret, fmt.Errorf("%v", goccyyaml.FormatError(err, true, true))
    31  		}
    32  		// XXX errors here are multiline, should we just print them to stderr instead of logging?
    33  		return ret, fmt.Errorf("%v", err)
    34  	}
    35  
    36  	// parse again because goccy is not strict enough anyway
    37  	dec2 := yaml.NewDecoder(bytes.NewBuffer(input))
    38  	dec2.KnownFields(true)
    39  
    40  	if err := dec2.Decode(&ret); err != nil {
    41  		return ret, fmt.Errorf("while unmarshaling setup file: %w", err)
    42  	}
    43  
    44  	return ret, nil
    45  }
    46  
    47  // InstallHubItems installs the objects recommended in a setup file.
    48  func InstallHubItems(hub *cwhub.Hub, input []byte, dryRun bool) error {
    49  	setupEnvelope, err := decodeSetup(input, false)
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	for _, setupItem := range setupEnvelope.Setup {
    55  		forceAction := false
    56  		downloadOnly := false
    57  		install := setupItem.Install
    58  
    59  		if install == nil {
    60  			continue
    61  		}
    62  
    63  		if len(install.Collections) > 0 {
    64  			for _, collection := range setupItem.Install.Collections {
    65  				item := hub.GetItem(cwhub.COLLECTIONS, collection)
    66  				if item == nil {
    67  					return fmt.Errorf("collection %s not found", collection)
    68  				}
    69  
    70  				if dryRun {
    71  					fmt.Println("dry-run: would install collection", collection)
    72  
    73  					continue
    74  				}
    75  
    76  				if err := item.Install(forceAction, downloadOnly); err != nil {
    77  					return fmt.Errorf("while installing collection %s: %w", item.Name, err)
    78  				}
    79  			}
    80  		}
    81  
    82  		if len(install.Parsers) > 0 {
    83  			for _, parser := range setupItem.Install.Parsers {
    84  				if dryRun {
    85  					fmt.Println("dry-run: would install parser", parser)
    86  
    87  					continue
    88  				}
    89  
    90  				item := hub.GetItem(cwhub.PARSERS, parser)
    91  				if item == nil {
    92  					return fmt.Errorf("parser %s not found", parser)
    93  				}
    94  
    95  				if err := item.Install(forceAction, downloadOnly); err != nil {
    96  					return fmt.Errorf("while installing parser %s: %w", item.Name, err)
    97  				}
    98  			}
    99  		}
   100  
   101  		if len(install.Scenarios) > 0 {
   102  			for _, scenario := range setupItem.Install.Scenarios {
   103  				if dryRun {
   104  					fmt.Println("dry-run: would install scenario", scenario)
   105  
   106  					continue
   107  				}
   108  
   109  				item := hub.GetItem(cwhub.SCENARIOS, scenario)
   110  				if item == nil {
   111  					return fmt.Errorf("scenario %s not found", scenario)
   112  				}
   113  
   114  				if err := item.Install(forceAction, downloadOnly); err != nil {
   115  					return fmt.Errorf("while installing scenario %s: %w", item.Name, err)
   116  				}
   117  			}
   118  		}
   119  
   120  		if len(install.PostOverflows) > 0 {
   121  			for _, postoverflow := range setupItem.Install.PostOverflows {
   122  				if dryRun {
   123  					fmt.Println("dry-run: would install postoverflow", postoverflow)
   124  
   125  					continue
   126  				}
   127  
   128  				item := hub.GetItem(cwhub.POSTOVERFLOWS, postoverflow)
   129  				if item == nil {
   130  					return fmt.Errorf("postoverflow %s not found", postoverflow)
   131  				}
   132  
   133  				if err := item.Install(forceAction, downloadOnly); err != nil {
   134  					return fmt.Errorf("while installing postoverflow %s: %w", item.Name, err)
   135  				}
   136  			}
   137  		}
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  // marshalAcquisDocuments creates the monolithic file, or itemized files (if a directory is provided) with the acquisition documents.
   144  func marshalAcquisDocuments(ads []AcquisDocument, toDir string) (string, error) {
   145  	var sb strings.Builder
   146  
   147  	dashTerminator := false
   148  
   149  	disclaimer := `
   150  #
   151  # This file was automatically generated by "cscli setup datasources".
   152  # You can modify it by hand, but will be responsible for its maintenance.
   153  # To add datasources or logfiles, you can instead write a new configuration
   154  # in the directory defined by acquisition_dir.
   155  #
   156  
   157  `
   158  
   159  	if toDir == "" {
   160  		sb.WriteString(disclaimer)
   161  	} else {
   162  		_, err := os.Stat(toDir)
   163  		if os.IsNotExist(err) {
   164  			return "", fmt.Errorf("directory %s does not exist", toDir)
   165  		}
   166  	}
   167  
   168  	for _, ad := range ads {
   169  		out, err := goccyyaml.MarshalWithOptions(ad.DataSource, goccyyaml.IndentSequence(true))
   170  		if err != nil {
   171  			return "", fmt.Errorf("while encoding datasource: %w", err)
   172  		}
   173  
   174  		if toDir != "" {
   175  			if ad.AcquisFilename == "" {
   176  				return "", fmt.Errorf("empty acquis filename")
   177  			}
   178  
   179  			fname := filepath.Join(toDir, ad.AcquisFilename)
   180  			fmt.Println("creating", fname)
   181  
   182  			f, err := os.Create(fname)
   183  			if err != nil {
   184  				return "", fmt.Errorf("creating acquisition file: %w", err)
   185  			}
   186  			defer f.Close()
   187  
   188  			_, err = f.WriteString(disclaimer)
   189  			if err != nil {
   190  				return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
   191  			}
   192  
   193  			_, err = f.Write(out)
   194  			if err != nil {
   195  				return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
   196  			}
   197  
   198  			f.Sync()
   199  
   200  			continue
   201  		}
   202  
   203  		if dashTerminator {
   204  			sb.WriteString("---\n")
   205  		}
   206  
   207  		sb.Write(out)
   208  
   209  		dashTerminator = true
   210  	}
   211  
   212  	return sb.String(), nil
   213  }
   214  
   215  // Validate checks the validity of a setup file.
   216  func Validate(input []byte) error {
   217  	_, err := decodeSetup(input, true)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  // DataSources generates the acquisition documents from a setup file.
   226  func DataSources(input []byte, toDir string) (string, error) {
   227  	setupEnvelope, err := decodeSetup(input, false)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  
   232  	ads := make([]AcquisDocument, 0)
   233  
   234  	filename := func(basename string, ext string) string {
   235  		if basename == "" {
   236  			return basename
   237  		}
   238  
   239  		return basename + ext
   240  	}
   241  
   242  	for _, setupItem := range setupEnvelope.Setup {
   243  		datasource := setupItem.DataSource
   244  
   245  		basename := ""
   246  		if toDir != "" {
   247  			basename = "setup." + setupItem.DetectedService
   248  		}
   249  
   250  		if datasource == nil {
   251  			continue
   252  		}
   253  
   254  		ad := AcquisDocument{
   255  			AcquisFilename: filename(basename, ".yaml"),
   256  			DataSource:     datasource,
   257  		}
   258  		ads = append(ads, ad)
   259  	}
   260  
   261  	return marshalAcquisDocuments(ads, toDir)
   262  }