github.com/crowdsecurity/crowdsec@v1.6.1/pkg/hubtest/scenario_assert.go (about) 1 package hubtest 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "os" 8 "sort" 9 "strings" 10 11 "github.com/antonmedv/expr" 12 log "github.com/sirupsen/logrus" 13 "gopkg.in/yaml.v2" 14 15 "github.com/crowdsecurity/crowdsec/pkg/dumps" 16 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 17 "github.com/crowdsecurity/crowdsec/pkg/types" 18 ) 19 20 type ScenarioAssert struct { 21 File string 22 AutoGenAssert bool 23 AutoGenAssertData string 24 NbAssert int 25 Fails []AssertFail 26 Success bool 27 TestData *BucketResults 28 PourData *dumps.BucketPourInfo 29 } 30 31 type BucketResults []types.Event 32 33 func NewScenarioAssert(file string) *ScenarioAssert { 34 ScenarioAssert := &ScenarioAssert{ 35 File: file, 36 NbAssert: 0, 37 Success: false, 38 Fails: make([]AssertFail, 0), 39 AutoGenAssert: false, 40 TestData: &BucketResults{}, 41 PourData: &dumps.BucketPourInfo{}, 42 } 43 44 return ScenarioAssert 45 } 46 47 func (s *ScenarioAssert) AutoGenFromFile(filename string) (string, error) { 48 err := s.LoadTest(filename, "") 49 if err != nil { 50 return "", err 51 } 52 53 ret := s.AutoGenScenarioAssert() 54 55 return ret, nil 56 } 57 58 func (s *ScenarioAssert) LoadTest(filename string, bucketpour string) error { 59 bucketDump, err := LoadScenarioDump(filename) 60 if err != nil { 61 return fmt.Errorf("loading scenario dump file '%s': %+v", filename, err) 62 } 63 64 s.TestData = bucketDump 65 66 if bucketpour != "" { 67 pourDump, err := dumps.LoadBucketPourDump(bucketpour) 68 if err != nil { 69 return fmt.Errorf("loading bucket pour dump file '%s': %+v", filename, err) 70 } 71 72 s.PourData = pourDump 73 } 74 75 return nil 76 } 77 78 func (s *ScenarioAssert) AssertFile(testFile string) error { 79 file, err := os.Open(s.File) 80 81 if err != nil { 82 return fmt.Errorf("failed to open") 83 } 84 85 if err := s.LoadTest(testFile, ""); err != nil { 86 return fmt.Errorf("unable to load parser dump file '%s': %s", testFile, err) 87 } 88 89 scanner := bufio.NewScanner(file) 90 scanner.Split(bufio.ScanLines) 91 92 nbLine := 0 93 94 for scanner.Scan() { 95 nbLine++ 96 97 if scanner.Text() == "" { 98 continue 99 } 100 101 ok, err := s.Run(scanner.Text()) 102 if err != nil { 103 return fmt.Errorf("unable to run assert '%s': %+v", scanner.Text(), err) 104 } 105 106 s.NbAssert++ 107 108 if !ok { 109 log.Debugf("%s is FALSE", scanner.Text()) 110 failedAssert := &AssertFail{ 111 File: s.File, 112 Line: nbLine, 113 Expression: scanner.Text(), 114 Debug: make(map[string]string), 115 } 116 117 match := variableRE.FindStringSubmatch(scanner.Text()) 118 119 if len(match) == 0 { 120 log.Infof("Couldn't get variable of line '%s'", scanner.Text()) 121 continue 122 } 123 124 variable := match[1] 125 126 result, err := s.EvalExpression(variable) 127 if err != nil { 128 log.Errorf("unable to evaluate variable '%s': %s", variable, err) 129 continue 130 } 131 132 failedAssert.Debug[variable] = result 133 s.Fails = append(s.Fails, *failedAssert) 134 135 continue 136 } 137 //fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text()) 138 } 139 140 file.Close() 141 142 if s.NbAssert == 0 { 143 assertData, err := s.AutoGenFromFile(testFile) 144 if err != nil { 145 return fmt.Errorf("couldn't generate assertion: %s", err) 146 } 147 148 s.AutoGenAssertData = assertData 149 s.AutoGenAssert = true 150 } 151 152 if len(s.Fails) == 0 { 153 s.Success = true 154 } 155 156 return nil 157 } 158 159 func (s *ScenarioAssert) RunExpression(expression string) (interface{}, error) { 160 //debug doesn't make much sense with the ability to evaluate "on the fly" 161 //var debugFilter *exprhelpers.ExprDebugger 162 var output interface{} 163 164 env := map[string]interface{}{"results": *s.TestData} 165 166 runtimeFilter, err := expr.Compile(expression, exprhelpers.GetExprOptions(env)...) 167 if err != nil { 168 return nil, err 169 } 170 // if debugFilter, err = exprhelpers.NewDebugger(assert, expr.Env(env)); err != nil { 171 // log.Warningf("Failed building debugher for %s : %s", assert, err) 172 // } 173 174 //dump opcode in trace level 175 log.Tracef("%s", runtimeFilter.Disassemble()) 176 177 output, err = expr.Run(runtimeFilter, map[string]interface{}{"results": *s.TestData}) 178 if err != nil { 179 log.Warningf("running : %s", expression) 180 log.Warningf("runtime error : %s", err) 181 182 return nil, fmt.Errorf("while running expression %s: %w", expression, err) 183 } 184 185 return output, nil 186 } 187 188 func (s *ScenarioAssert) EvalExpression(expression string) (string, error) { 189 output, err := s.RunExpression(expression) 190 if err != nil { 191 return "", err 192 } 193 194 ret, err := yaml.Marshal(output) 195 if err != nil { 196 return "", err 197 } 198 199 return string(ret), nil 200 } 201 202 func (s *ScenarioAssert) Run(assert string) (bool, error) { 203 output, err := s.RunExpression(assert) 204 if err != nil { 205 return false, err 206 } 207 208 switch out := output.(type) { 209 case bool: 210 return out, nil 211 default: 212 return false, fmt.Errorf("assertion '%s' is not a condition", assert) 213 } 214 } 215 216 func (s *ScenarioAssert) AutoGenScenarioAssert() string { 217 // attempt to autogen scenario asserts 218 ret := fmt.Sprintf(`len(results) == %d`+"\n", len(*s.TestData)) 219 220 for eventIndex, event := range *s.TestData { 221 for ipSrc, source := range event.Overflow.Sources { 222 ret += fmt.Sprintf(`"%s" in results[%d].Overflow.GetSources()`+"\n", ipSrc, eventIndex) 223 ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].IP == "%s"`+"\n", eventIndex, ipSrc, source.IP) 224 ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].Range == "%s"`+"\n", eventIndex, ipSrc, source.Range) 225 ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].GetScope() == "%s"`+"\n", eventIndex, ipSrc, *source.Scope) 226 ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].GetValue() == "%s"`+"\n", eventIndex, ipSrc, *source.Value) 227 } 228 229 for evtIndex, evt := range event.Overflow.Alert.Events { 230 for _, meta := range evt.Meta { 231 ret += fmt.Sprintf(`results[%d].Overflow.Alert.Events[%d].GetMeta("%s") == "%s"`+"\n", eventIndex, evtIndex, meta.Key, Escape(meta.Value)) 232 } 233 } 234 235 ret += fmt.Sprintf(`results[%d].Overflow.Alert.GetScenario() == "%s"`+"\n", eventIndex, *event.Overflow.Alert.Scenario) 236 ret += fmt.Sprintf(`results[%d].Overflow.Alert.Remediation == %t`+"\n", eventIndex, event.Overflow.Alert.Remediation) 237 ret += fmt.Sprintf(`results[%d].Overflow.Alert.GetEventsCount() == %d`+"\n", eventIndex, *event.Overflow.Alert.EventsCount) 238 } 239 240 return ret 241 } 242 243 func (b BucketResults) Len() int { 244 return len(b) 245 } 246 247 func (b BucketResults) Less(i, j int) bool { 248 return b[i].Overflow.Alert.GetScenario()+strings.Join(b[i].Overflow.GetSources(), "@") > b[j].Overflow.Alert.GetScenario()+strings.Join(b[j].Overflow.GetSources(), "@") 249 } 250 251 func (b BucketResults) Swap(i, j int) { 252 b[i], b[j] = b[j], b[i] 253 } 254 255 func LoadScenarioDump(filepath string) (*BucketResults, error) { 256 dumpData, err := os.Open(filepath) 257 if err != nil { 258 return nil, err 259 } 260 defer dumpData.Close() 261 262 results, err := io.ReadAll(dumpData) 263 if err != nil { 264 return nil, err 265 } 266 267 var bucketDump BucketResults 268 269 if err := yaml.Unmarshal(results, &bucketDump); err != nil { 270 return nil, err 271 } 272 273 sort.Sort(bucketDump) 274 275 return &bucketDump, nil 276 }