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 }