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  }