github.com/crowdsecurity/crowdsec@v1.6.1/pkg/dumps/parser_dump.go (about) 1 package dumps 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "sort" 9 "strings" 10 "time" 11 12 "github.com/fatih/color" 13 diff "github.com/r3labs/diff/v2" 14 log "github.com/sirupsen/logrus" 15 "gopkg.in/yaml.v2" 16 17 "github.com/crowdsecurity/go-cs-lib/maptools" 18 19 "github.com/crowdsecurity/crowdsec/pkg/emoji" 20 "github.com/crowdsecurity/crowdsec/pkg/types" 21 ) 22 23 type ParserResult struct { 24 Idx int 25 Evt types.Event 26 Success bool 27 } 28 29 type ParserResults map[string]map[string][]ParserResult 30 31 type DumpOpts struct { 32 Details bool 33 SkipOk bool 34 ShowNotOkParsers bool 35 } 36 37 func LoadParserDump(filepath string) (*ParserResults, error) { 38 dumpData, err := os.Open(filepath) 39 if err != nil { 40 return nil, err 41 } 42 defer dumpData.Close() 43 44 results, err := io.ReadAll(dumpData) 45 if err != nil { 46 return nil, err 47 } 48 49 pdump := ParserResults{} 50 51 if err := yaml.Unmarshal(results, &pdump); err != nil { 52 return nil, err 53 } 54 55 /* we know that some variables should always be set, 56 let's check if they're present in last parser output of last stage */ 57 58 stages := maptools.SortedKeys(pdump) 59 60 var lastStage string 61 62 // Loop over stages to find last successful one with at least one parser 63 for i := len(stages) - 2; i >= 0; i-- { 64 if len(pdump[stages[i]]) != 0 { 65 lastStage = stages[i] 66 break 67 } 68 } 69 70 parsers := make([]string, 0, len(pdump[lastStage])) 71 72 for k := range pdump[lastStage] { 73 parsers = append(parsers, k) 74 } 75 76 sort.Strings(parsers) 77 78 if len(parsers) == 0 { 79 return nil, errors.New("no parser found. Please install the appropriate parser and retry") 80 } 81 82 lastParser := parsers[len(parsers)-1] 83 84 for idx, result := range pdump[lastStage][lastParser] { 85 if result.Evt.StrTime == "" { 86 log.Warningf("Line %d/%d is missing evt.StrTime. It is most likely a mistake as it will prevent your logs to be processed in time-machine/forensic mode.", idx, len(pdump[lastStage][lastParser])) 87 } else { 88 log.Debugf("Line %d/%d has evt.StrTime set to '%s'", idx, len(pdump[lastStage][lastParser]), result.Evt.StrTime) 89 } 90 } 91 92 return &pdump, nil 93 } 94 95 func DumpTree(parserResults ParserResults, bucketPour BucketPourInfo, opts DumpOpts) { 96 // note : we can use line -> time as the unique identifier (of acquisition) 97 state := make(map[time.Time]map[string]map[string]ParserResult) 98 assoc := make(map[time.Time]string, 0) 99 parser_order := make(map[string][]string) 100 101 for stage, parsers := range parserResults { 102 // let's process parsers in the order according to idx 103 parser_order[stage] = make([]string, len(parsers)) 104 105 for pname, parser := range parsers { 106 if len(parser) > 0 { 107 parser_order[stage][parser[0].Idx-1] = pname 108 } 109 } 110 111 for _, parser := range parser_order[stage] { 112 results := parsers[parser] 113 for _, parserRes := range results { 114 evt := parserRes.Evt 115 if _, ok := state[evt.Line.Time]; !ok { 116 state[evt.Line.Time] = make(map[string]map[string]ParserResult) 117 assoc[evt.Line.Time] = evt.Line.Raw 118 } 119 120 if _, ok := state[evt.Line.Time][stage]; !ok { 121 state[evt.Line.Time][stage] = make(map[string]ParserResult) 122 } 123 124 state[evt.Line.Time][stage][parser] = ParserResult{Evt: evt, Success: parserRes.Success} 125 } 126 } 127 } 128 129 for bname, evtlist := range bucketPour { 130 for _, evt := range evtlist { 131 if evt.Line.Raw == "" { 132 continue 133 } 134 135 // it might be bucket overflow being reprocessed, skip this 136 if _, ok := state[evt.Line.Time]; !ok { 137 state[evt.Line.Time] = make(map[string]map[string]ParserResult) 138 assoc[evt.Line.Time] = evt.Line.Raw 139 } 140 141 // there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase 142 // we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered 143 if _, ok := state[evt.Line.Time]["buckets"]; !ok { 144 state[evt.Line.Time]["buckets"] = make(map[string]ParserResult) 145 } 146 147 state[evt.Line.Time]["buckets"][bname] = ParserResult{Success: true} 148 } 149 } 150 151 yellow := color.New(color.FgYellow).SprintFunc() 152 red := color.New(color.FgRed).SprintFunc() 153 green := color.New(color.FgGreen).SprintFunc() 154 whitelistReason := "" 155 // get each line 156 for tstamp, rawstr := range assoc { 157 if opts.SkipOk { 158 if _, ok := state[tstamp]["buckets"]["OK"]; ok { 159 continue 160 } 161 } 162 163 fmt.Printf("line: %s\n", rawstr) 164 165 skeys := make([]string, 0, len(state[tstamp])) 166 167 for k := range state[tstamp] { 168 // there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase 169 // we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered 170 if k == "buckets" { 171 continue 172 } 173 174 skeys = append(skeys, k) 175 } 176 177 sort.Strings(skeys) 178 179 // iterate stage 180 var prevItem types.Event 181 182 for _, stage := range skeys { 183 parsers := state[tstamp][stage] 184 185 sep := "├" 186 presep := "|" 187 188 fmt.Printf("\t%s %s\n", sep, stage) 189 190 for idx, parser := range parser_order[stage] { 191 res := parsers[parser].Success 192 sep := "├" 193 194 if idx == len(parser_order[stage])-1 { 195 sep = "└" 196 } 197 198 created := 0 199 updated := 0 200 deleted := 0 201 whitelisted := false 202 changeStr := "" 203 detailsDisplay := "" 204 205 if res { 206 changelog, _ := diff.Diff(prevItem, parsers[parser].Evt) 207 for _, change := range changelog { 208 switch change.Type { 209 case "create": 210 created++ 211 212 detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), green(change.To)) 213 case "update": 214 detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s -> %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), change.From, yellow(change.To)) 215 216 if change.Path[0] == "Whitelisted" && change.To == true { 217 whitelisted = true 218 219 if whitelistReason == "" { 220 whitelistReason = parsers[parser].Evt.WhitelistReason 221 } 222 } 223 224 updated++ 225 case "delete": 226 deleted++ 227 228 detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s\n", presep, sep, change.Type, red(strings.Join(change.Path, "."))) 229 } 230 } 231 232 prevItem = parsers[parser].Evt 233 } 234 235 if created > 0 { 236 changeStr += green(fmt.Sprintf("+%d", created)) 237 } 238 239 if updated > 0 { 240 if len(changeStr) > 0 { 241 changeStr += " " 242 } 243 244 changeStr += yellow(fmt.Sprintf("~%d", updated)) 245 } 246 247 if deleted > 0 { 248 if len(changeStr) > 0 { 249 changeStr += " " 250 } 251 252 changeStr += red(fmt.Sprintf("-%d", deleted)) 253 } 254 255 if whitelisted { 256 if len(changeStr) > 0 { 257 changeStr += " " 258 } 259 260 changeStr += red("[whitelisted]") 261 } 262 263 if changeStr == "" { 264 changeStr = yellow("unchanged") 265 } 266 267 if res { 268 fmt.Printf("\t%s\t%s %s %s (%s)\n", presep, sep, emoji.GreenCircle, parser, changeStr) 269 270 if opts.Details { 271 fmt.Print(detailsDisplay) 272 } 273 } else if opts.ShowNotOkParsers { 274 fmt.Printf("\t%s\t%s %s %s\n", presep, sep, emoji.RedCircle, parser) 275 } 276 } 277 } 278 279 sep := "└" 280 281 if len(state[tstamp]["buckets"]) > 0 { 282 sep = "├" 283 } 284 285 // did the event enter the bucket pour phase ? 286 if _, ok := state[tstamp]["buckets"]["OK"]; ok { 287 fmt.Printf("\t%s-------- parser success %s\n", sep, emoji.GreenCircle) 288 } else if whitelistReason != "" { 289 fmt.Printf("\t%s-------- parser success, ignored by whitelist (%s) %s\n", sep, whitelistReason, emoji.GreenCircle) 290 } else { 291 fmt.Printf("\t%s-------- parser failure %s\n", sep, emoji.RedCircle) 292 } 293 294 // now print bucket info 295 if len(state[tstamp]["buckets"]) > 0 { 296 fmt.Printf("\t├ Scenarios\n") 297 } 298 299 bnames := make([]string, 0, len(state[tstamp]["buckets"])) 300 301 for k := range state[tstamp]["buckets"] { 302 // there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase 303 // we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered 304 if k == "OK" { 305 continue 306 } 307 308 bnames = append(bnames, k) 309 } 310 311 sort.Strings(bnames) 312 313 for idx, bname := range bnames { 314 sep := "├" 315 if idx == len(bnames)-1 { 316 sep = "└" 317 } 318 319 fmt.Printf("\t\t%s %s %s\n", sep, emoji.GreenCircle, bname) 320 } 321 322 fmt.Println() 323 } 324 }