github.com/crowdsecurity/crowdsec@v1.6.1/pkg/hubtest/parser_assert.go (about) 1 package hubtest 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/antonmedv/expr" 10 log "github.com/sirupsen/logrus" 11 "gopkg.in/yaml.v2" 12 13 "github.com/crowdsecurity/crowdsec/pkg/dumps" 14 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 15 "github.com/crowdsecurity/go-cs-lib/maptools" 16 ) 17 18 type AssertFail struct { 19 File string 20 Line int 21 Expression string 22 Debug map[string]string 23 } 24 25 type ParserAssert struct { 26 File string 27 AutoGenAssert bool 28 AutoGenAssertData string 29 NbAssert int 30 Fails []AssertFail 31 Success bool 32 TestData *dumps.ParserResults 33 } 34 35 func NewParserAssert(file string) *ParserAssert { 36 ParserAssert := &ParserAssert{ 37 File: file, 38 NbAssert: 0, 39 Success: false, 40 Fails: make([]AssertFail, 0), 41 AutoGenAssert: false, 42 TestData: &dumps.ParserResults{}, 43 } 44 45 return ParserAssert 46 } 47 48 func (p *ParserAssert) AutoGenFromFile(filename string) (string, error) { 49 err := p.LoadTest(filename) 50 if err != nil { 51 return "", err 52 } 53 54 ret := p.AutoGenParserAssert() 55 56 return ret, nil 57 } 58 59 func (p *ParserAssert) LoadTest(filename string) error { 60 parserDump, err := dumps.LoadParserDump(filename) 61 if err != nil { 62 return fmt.Errorf("loading parser dump file: %+v", err) 63 } 64 65 p.TestData = parserDump 66 67 return nil 68 } 69 70 func (p *ParserAssert) AssertFile(testFile string) error { 71 file, err := os.Open(p.File) 72 73 if err != nil { 74 return fmt.Errorf("failed to open") 75 } 76 77 if err := p.LoadTest(testFile); err != nil { 78 return fmt.Errorf("unable to load parser dump file '%s': %s", testFile, err) 79 } 80 81 scanner := bufio.NewScanner(file) 82 scanner.Split(bufio.ScanLines) 83 84 nbLine := 0 85 86 for scanner.Scan() { 87 nbLine++ 88 89 if scanner.Text() == "" { 90 continue 91 } 92 93 ok, err := p.Run(scanner.Text()) 94 if err != nil { 95 return fmt.Errorf("unable to run assert '%s': %+v", scanner.Text(), err) 96 } 97 98 p.NbAssert++ 99 100 if !ok { 101 log.Debugf("%s is FALSE", scanner.Text()) 102 failedAssert := &AssertFail{ 103 File: p.File, 104 Line: nbLine, 105 Expression: scanner.Text(), 106 Debug: make(map[string]string), 107 } 108 109 match := variableRE.FindStringSubmatch(scanner.Text()) 110 var variable string 111 112 if len(match) == 0 { 113 log.Infof("Couldn't get variable of line '%s'", scanner.Text()) 114 variable = scanner.Text() 115 } else { 116 variable = match[1] 117 } 118 119 result, err := p.EvalExpression(variable) 120 if err != nil { 121 log.Errorf("unable to evaluate variable '%s': %s", variable, err) 122 continue 123 } 124 125 failedAssert.Debug[variable] = result 126 p.Fails = append(p.Fails, *failedAssert) 127 128 continue 129 } 130 //fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text()) 131 } 132 133 file.Close() 134 135 if p.NbAssert == 0 { 136 assertData, err := p.AutoGenFromFile(testFile) 137 if err != nil { 138 return fmt.Errorf("couldn't generate assertion: %s", err) 139 } 140 141 p.AutoGenAssertData = assertData 142 p.AutoGenAssert = true 143 } 144 145 if len(p.Fails) == 0 { 146 p.Success = true 147 } 148 149 return nil 150 } 151 152 func (p *ParserAssert) RunExpression(expression string) (interface{}, error) { 153 //debug doesn't make much sense with the ability to evaluate "on the fly" 154 //var debugFilter *exprhelpers.ExprDebugger 155 var output interface{} 156 157 env := map[string]interface{}{"results": *p.TestData} 158 159 runtimeFilter, err := expr.Compile(expression, exprhelpers.GetExprOptions(env)...) 160 if err != nil { 161 log.Errorf("failed to compile '%s' : %s", expression, err) 162 return output, err 163 } 164 165 //dump opcode in trace level 166 log.Tracef("%s", runtimeFilter.Disassemble()) 167 168 output, err = expr.Run(runtimeFilter, env) 169 if err != nil { 170 log.Warningf("running : %s", expression) 171 log.Warningf("runtime error : %s", err) 172 173 return output, fmt.Errorf("while running expression %s: %w", expression, err) 174 } 175 176 return output, nil 177 } 178 179 func (p *ParserAssert) EvalExpression(expression string) (string, error) { 180 output, err := p.RunExpression(expression) 181 if err != nil { 182 return "", err 183 } 184 185 ret, err := yaml.Marshal(output) 186 187 if err != nil { 188 return "", err 189 } 190 191 return string(ret), nil 192 } 193 194 func (p *ParserAssert) Run(assert string) (bool, error) { 195 output, err := p.RunExpression(assert) 196 if err != nil { 197 return false, err 198 } 199 200 switch out := output.(type) { 201 case bool: 202 return out, nil 203 default: 204 return false, fmt.Errorf("assertion '%s' is not a condition", assert) 205 } 206 } 207 208 func Escape(val string) string { 209 val = strings.ReplaceAll(val, `\`, `\\`) 210 val = strings.ReplaceAll(val, `"`, `\"`) 211 212 return val 213 } 214 215 func (p *ParserAssert) AutoGenParserAssert() string { 216 //attempt to autogen parser asserts 217 ret := fmt.Sprintf("len(results) == %d\n", len(*p.TestData)) 218 219 //sort map keys for consistent order 220 stages := maptools.SortedKeys(*p.TestData) 221 222 for _, stage := range stages { 223 parsers := (*p.TestData)[stage] 224 225 //sort map keys for consistent order 226 pnames := maptools.SortedKeys(parsers) 227 228 for _, parser := range pnames { 229 presults := parsers[parser] 230 ret += fmt.Sprintf(`len(results["%s"]["%s"]) == %d`+"\n", stage, parser, len(presults)) 231 232 for pidx, result := range presults { 233 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Success == %t`+"\n", stage, parser, pidx, result.Success) 234 235 if !result.Success { 236 continue 237 } 238 239 for _, pkey := range maptools.SortedKeys(result.Evt.Parsed) { 240 pval := result.Evt.Parsed[pkey] 241 if pval == "" { 242 continue 243 } 244 245 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s"`+"\n", stage, parser, pidx, pkey, Escape(pval)) 246 } 247 248 for _, mkey := range maptools.SortedKeys(result.Evt.Meta) { 249 mval := result.Evt.Meta[mkey] 250 if mval == "" { 251 continue 252 } 253 254 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s"`+"\n", stage, parser, pidx, mkey, Escape(mval)) 255 } 256 257 for _, ekey := range maptools.SortedKeys(result.Evt.Enriched) { 258 eval := result.Evt.Enriched[ekey] 259 if eval == "" { 260 continue 261 } 262 263 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s"`+"\n", stage, parser, pidx, ekey, Escape(eval)) 264 } 265 266 for _, ukey := range maptools.SortedKeys(result.Evt.Unmarshaled) { 267 uval := result.Evt.Unmarshaled[ukey] 268 if uval == "" { 269 continue 270 } 271 272 base := fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Unmarshaled["%s"]`, stage, parser, pidx, ukey) 273 274 for _, line := range p.buildUnmarshaledAssert(base, uval) { 275 ret += line 276 } 277 } 278 279 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Whitelisted == %t`+"\n", stage, parser, pidx, result.Evt.Whitelisted) 280 281 if result.Evt.WhitelistReason != "" { 282 ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.WhitelistReason == "%s"`+"\n", stage, parser, pidx, Escape(result.Evt.WhitelistReason)) 283 } 284 } 285 } 286 } 287 288 return ret 289 } 290 291 func (p *ParserAssert) buildUnmarshaledAssert(ekey string, eval interface{}) []string { 292 ret := make([]string, 0) 293 294 switch val := eval.(type) { 295 case map[string]interface{}: 296 for k, v := range val { 297 ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...) 298 } 299 case map[interface{}]interface{}: 300 for k, v := range val { 301 ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...) 302 } 303 case []interface{}: 304 case string: 305 ret = append(ret, fmt.Sprintf(`%s == "%s"`+"\n", ekey, Escape(val))) 306 case bool: 307 ret = append(ret, fmt.Sprintf(`%s == %t`+"\n", ekey, val)) 308 case int: 309 ret = append(ret, fmt.Sprintf(`%s == %d`+"\n", ekey, val)) 310 case float64: 311 ret = append(ret, fmt.Sprintf(`FloatApproxEqual(%s, %f)`+"\n", 312 ekey, val)) 313 default: 314 log.Warningf("unknown type '%T' for key '%s'", val, ekey) 315 } 316 317 return ret 318 }