github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/exprlib_test.go (about) 1 package exprhelpers 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/antonmedv/expr" 11 "github.com/pkg/errors" 12 log "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/crowdsecurity/go-cs-lib/cstest" 17 "github.com/crowdsecurity/go-cs-lib/ptr" 18 19 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 20 "github.com/crowdsecurity/crowdsec/pkg/database" 21 "github.com/crowdsecurity/crowdsec/pkg/models" 22 "github.com/crowdsecurity/crowdsec/pkg/types" 23 ) 24 25 var ( 26 TestFolder = "tests" 27 ) 28 29 func getDBClient(t *testing.T) *database.Client { 30 t.Helper() 31 32 dbPath, err := os.CreateTemp("", "*sqlite") 33 require.NoError(t, err) 34 35 testDBClient, err := database.NewClient(&csconfig.DatabaseCfg{ 36 Type: "sqlite", 37 DbName: "crowdsec", 38 DbPath: dbPath.Name(), 39 }) 40 require.NoError(t, err) 41 42 return testDBClient 43 } 44 45 func TestVisitor(t *testing.T) { 46 err := Init(nil) 47 require.NoError(t, err) 48 49 tests := []struct { 50 name string 51 filter string 52 result bool 53 env map[string]interface{} 54 err error 55 }{ 56 { 57 name: "debug : no variable", 58 filter: "'crowdsec' startsWith 'crowdse'", 59 result: true, 60 err: nil, 61 env: map[string]interface{}{}, 62 }, 63 { 64 name: "debug : simple variable", 65 filter: "'crowdsec' startsWith static_one && 1 == 1", 66 result: true, 67 err: nil, 68 env: map[string]interface{}{"static_one": string("crowdse")}, 69 }, 70 { 71 name: "debug : simple variable re-used", 72 filter: "static_one.foo == 'bar' && static_one.foo != 'toto'", 73 result: true, 74 err: nil, 75 env: map[string]interface{}{"static_one": map[string]string{"foo": "bar"}}, 76 }, 77 { 78 name: "debug : can't compile", 79 filter: "static_one.foo.toto == 'lol'", 80 result: false, 81 err: fmt.Errorf("bad syntax"), 82 env: map[string]interface{}{"static_one": map[string]string{"foo": "bar"}}, 83 }, 84 { 85 name: "debug : can't compile #2", 86 filter: "static_one.f!oo.to/to == 'lol'", 87 result: false, 88 err: fmt.Errorf("bad syntax"), 89 env: map[string]interface{}{"static_one": map[string]string{"foo": "bar"}}, 90 }, 91 { 92 name: "debug : can't compile #3", 93 filter: "", 94 result: false, 95 err: fmt.Errorf("bad syntax"), 96 env: map[string]interface{}{"static_one": map[string]string{"foo": "bar"}}, 97 }, 98 } 99 100 log.SetLevel(log.DebugLevel) 101 102 for _, test := range tests { 103 compiledFilter, err := expr.Compile(test.filter, GetExprOptions(test.env)...) 104 if err != nil && test.err == nil { 105 log.Fatalf("compile: %s", err) 106 } 107 108 if compiledFilter != nil { 109 result, err := expr.Run(compiledFilter, test.env) 110 if err != nil && test.err == nil { 111 log.Fatalf("run : %s", err) 112 } 113 114 if isOk := assert.Equal(t, test.result, result); !isOk { 115 t.Fatalf("test '%s' : NOK", test.filter) 116 } 117 } 118 } 119 } 120 121 func TestMatch(t *testing.T) { 122 err := Init(nil) 123 require.NoError(t, err) 124 125 tests := []struct { 126 glob string 127 val string 128 ret bool 129 expr string 130 }{ 131 {"foo", "foo", true, `Match(pattern, name)`}, 132 {"foo", "bar", false, `Match(pattern, name)`}, 133 {"foo*", "foo", true, `Match(pattern, name)`}, 134 {"foo*", "foobar", true, `Match(pattern, name)`}, 135 {"foo*", "barfoo", false, `Match(pattern, name)`}, 136 {"foo*", "bar", false, `Match(pattern, name)`}, 137 {"*foo", "foo", true, `Match(pattern, name)`}, 138 {"*foo", "barfoo", true, `Match(pattern, name)`}, 139 {"foo*r", "foobar", true, `Match(pattern, name)`}, 140 {"foo*r", "foobazr", true, `Match(pattern, name)`}, 141 {"foo?ar", "foobar", true, `Match(pattern, name)`}, 142 {"foo?ar", "foobazr", false, `Match(pattern, name)`}, 143 {"foo?ar", "foobaz", false, `Match(pattern, name)`}, 144 {"*foo?ar?", "foobar", false, `Match(pattern, name)`}, 145 {"*foo?ar?", "foobare", true, `Match(pattern, name)`}, 146 {"*foo?ar?", "rafoobar", false, `Match(pattern, name)`}, 147 {"*foo?ar?", "rafoobare", true, `Match(pattern, name)`}, 148 } 149 for _, test := range tests { 150 env := map[string]interface{}{ 151 "pattern": test.glob, 152 "name": test.val, 153 } 154 155 vm, err := expr.Compile(test.expr, GetExprOptions(env)...) 156 if err != nil { 157 t.Fatalf("pattern:%s val:%s NOK %s", test.glob, test.val, err) 158 } 159 160 ret, err := expr.Run(vm, env) 161 require.NoError(t, err) 162 163 if isOk := assert.Equal(t, test.ret, ret); !isOk { 164 t.Fatalf("pattern:%s val:%s NOK %t != %t", test.glob, test.val, ret, test.ret) 165 } 166 } 167 } 168 169 func TestDistanceHelper(t *testing.T) { 170 err := Init(nil) 171 require.NoError(t, err) 172 173 tests := []struct { 174 lat1 string 175 lon1 string 176 lat2 string 177 lon2 string 178 dist float64 179 valid bool 180 expr string 181 name string 182 }{ 183 {"51.45", "1.15", "41.54", "12.27", 1389.1793118293067, true, `Distance(lat1, lon1, lat2, lon2)`, "valid"}, 184 {"lol", "1.15", "41.54", "12.27", 0.0, false, `Distance(lat1, lon1, lat2, lon2)`, "invalid lat1"}, 185 {"0.0", "0.0", "12.1", "12.1", 0.0, true, `Distance(lat1, lon1, lat2, lon2)`, "empty coord"}, 186 } 187 188 for _, test := range tests { 189 t.Run(test.name, func(t *testing.T) { 190 env := map[string]interface{}{ 191 "lat1": test.lat1, 192 "lon1": test.lon1, 193 "lat2": test.lat2, 194 "lon2": test.lon2, 195 } 196 vm, err := expr.Compile(test.expr, GetExprOptions(env)...) 197 if err != nil { 198 t.Fatalf("pattern:%s val:%s NOK %s", test.lat1, test.lon1, err) 199 } 200 ret, err := expr.Run(vm, env) 201 if test.valid { 202 require.NoError(t, err) 203 assert.InDelta(t, test.dist, ret, 0.000001) 204 } else { 205 require.Error(t, err) 206 } 207 }) 208 } 209 } 210 211 func TestRegexpCacheBehavior(t *testing.T) { 212 err := Init(nil) 213 require.NoError(t, err) 214 215 filename := "test_data_re.txt" 216 err = FileInit(TestFolder, filename, "regex") 217 require.NoError(t, err) 218 219 //cache with no TTL 220 err = RegexpCacheInit(filename, types.DataSource{Type: "regex", Size: ptr.Of(1)}) 221 require.NoError(t, err) 222 223 ret, _ := RegexpInFile("crowdsec", filename) 224 assert.False(t, ret.(bool)) 225 assert.Equal(t, 1, dataFileRegexCache[filename].Len(false)) 226 227 ret, _ = RegexpInFile("Crowdsec", filename) 228 assert.True(t, ret.(bool)) 229 assert.Equal(t, 1, dataFileRegexCache[filename].Len(false)) 230 231 //cache with TTL 232 ttl := 500 * time.Millisecond 233 err = RegexpCacheInit(filename, types.DataSource{Type: "regex", Size: ptr.Of(2), TTL: &ttl}) 234 require.NoError(t, err) 235 236 ret, _ = RegexpInFile("crowdsec", filename) 237 assert.False(t, ret.(bool)) 238 assert.Equal(t, 1, dataFileRegexCache[filename].Len(true)) 239 240 time.Sleep(1 * time.Second) 241 assert.Equal(t, 0, dataFileRegexCache[filename].Len(true)) 242 } 243 244 func TestRegexpInFile(t *testing.T) { 245 if err := Init(nil); err != nil { 246 log.Fatal(err) 247 } 248 249 err := FileInit(TestFolder, "test_data_re.txt", "regex") 250 if err != nil { 251 log.Fatal(err) 252 } 253 254 tests := []struct { 255 name string 256 filter string 257 result bool 258 err error 259 }{ 260 { 261 name: "RegexpInFile() test: lower case word in data file", 262 filter: "RegexpInFile('crowdsec', 'test_data_re.txt')", 263 result: false, 264 err: nil, 265 }, 266 { 267 name: "RegexpInFile() test: Match exactly", 268 filter: "RegexpInFile('Crowdsec', 'test_data_re.txt')", 269 result: true, 270 err: nil, 271 }, 272 { 273 name: "RegexpInFile() test: match with word before", 274 filter: "RegexpInFile('test Crowdsec', 'test_data_re.txt')", 275 result: true, 276 err: nil, 277 }, 278 { 279 name: "RegexpInFile() test: match with word before and other case", 280 filter: "RegexpInFile('test CrowdSec', 'test_data_re.txt')", 281 result: true, 282 err: nil, 283 }, 284 } 285 286 for _, test := range tests { 287 compiledFilter, err := expr.Compile(test.filter, GetExprOptions(map[string]interface{}{})...) 288 if err != nil { 289 log.Fatal(err) 290 } 291 292 result, err := expr.Run(compiledFilter, map[string]interface{}{}) 293 if err != nil { 294 log.Fatal(err) 295 } 296 297 if isOk := assert.Equal(t, test.result, result); !isOk { 298 t.Fatalf("test '%s' : NOK", test.name) 299 } 300 } 301 } 302 303 func TestFileInit(t *testing.T) { 304 if err := Init(nil); err != nil { 305 log.Fatal(err) 306 } 307 308 tests := []struct { 309 name string 310 filename string 311 types string 312 result int 313 err error 314 }{ 315 { 316 name: "file with type:string", 317 filename: "test_data.txt", 318 types: "string", 319 result: 3, 320 }, 321 { 322 name: "file with type:string and empty lines + commentaries", 323 filename: "test_empty_line.txt", 324 types: "string", 325 result: 3, 326 }, 327 { 328 name: "file with type:re", 329 filename: "test_data_re.txt", 330 types: "regex", 331 result: 2, 332 }, 333 { 334 name: "file without type", 335 filename: "test_data_no_type.txt", 336 types: "", 337 }, 338 } 339 340 for _, test := range tests { 341 err := FileInit(TestFolder, test.filename, test.types) 342 if err != nil { 343 log.Fatal(err) 344 } 345 346 switch test.types { 347 case "string": 348 if _, ok := dataFile[test.filename]; !ok { 349 t.Fatalf("test '%s' : NOK", test.name) 350 } 351 352 if isOk := assert.Len(t, dataFile[test.filename], test.result); !isOk { 353 t.Fatalf("test '%s' : NOK", test.name) 354 } 355 case "regex": 356 if _, ok := dataFileRegex[test.filename]; !ok { 357 t.Fatalf("test '%s' : NOK", test.name) 358 } 359 360 if isOk := assert.Len(t, dataFileRegex[test.filename], test.result); !isOk { 361 t.Fatalf("test '%s' : NOK", test.name) 362 } 363 default: 364 if _, ok := dataFileRegex[test.filename]; ok { 365 t.Fatalf("test '%s' : NOK", test.name) 366 } 367 368 if _, ok := dataFile[test.filename]; ok { 369 t.Fatalf("test '%s' : NOK", test.name) 370 } 371 } 372 373 log.Printf("test '%s' : OK", test.name) 374 } 375 } 376 377 func TestFile(t *testing.T) { 378 if err := Init(nil); err != nil { 379 log.Fatal(err) 380 } 381 382 err := FileInit(TestFolder, "test_data.txt", "string") 383 if err != nil { 384 log.Fatal(err) 385 } 386 387 tests := []struct { 388 name string 389 filter string 390 result bool 391 err error 392 }{ 393 { 394 name: "File() test: word in file", 395 filter: "'Crowdsec' in File('test_data.txt')", 396 result: true, 397 err: nil, 398 }, 399 { 400 name: "File() test: word in file but different case", 401 filter: "'CrowdSecurity' in File('test_data.txt')", 402 result: false, 403 err: nil, 404 }, 405 { 406 name: "File() test: word not in file", 407 filter: "'test' in File('test_data.txt')", 408 result: false, 409 err: nil, 410 }, 411 { 412 name: "File() test: filepath provided doesn't exist", 413 filter: "'test' in File('non_existing_data.txt')", 414 result: false, 415 err: nil, 416 }, 417 } 418 419 for _, test := range tests { 420 compiledFilter, err := expr.Compile(test.filter, GetExprOptions(map[string]interface{}{})...) 421 if err != nil { 422 log.Fatal(err) 423 } 424 425 result, err := expr.Run(compiledFilter, map[string]interface{}{}) 426 if err != nil { 427 log.Fatal(err) 428 } 429 430 if isOk := assert.Equal(t, test.result, result); !isOk { 431 t.Fatalf("test '%s' : NOK", test.name) 432 } 433 434 log.Printf("test '%s' : OK", test.name) 435 } 436 } 437 438 func TestIpInRange(t *testing.T) { 439 err := Init(nil) 440 require.NoError(t, err) 441 tests := []struct { 442 name string 443 env map[string]interface{} 444 code string 445 result bool 446 err string 447 }{ 448 { 449 name: "IpInRange() test: basic test", 450 env: map[string]interface{}{ 451 "ip": "192.168.0.1", 452 "ipRange": "192.168.0.0/24", 453 }, 454 code: "IpInRange(ip, ipRange)", 455 result: true, 456 err: "", 457 }, 458 { 459 name: "IpInRange() test: malformed IP", 460 env: map[string]interface{}{ 461 "ip": "192.168.0", 462 "ipRange": "192.168.0.0/24", 463 }, 464 code: "IpInRange(ip, ipRange)", 465 result: false, 466 err: "", 467 }, 468 { 469 name: "IpInRange() test: malformed IP range", 470 env: map[string]interface{}{ 471 "ip": "192.168.0.0/255", 472 "ipRange": "192.168.0.0/24", 473 }, 474 code: "IpInRange(ip, ipRange)", 475 result: false, 476 err: "", 477 }, 478 } 479 480 for _, test := range tests { 481 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 482 require.NoError(t, err) 483 output, err := expr.Run(program, test.env) 484 require.NoError(t, err) 485 require.Equal(t, test.result, output) 486 log.Printf("test '%s' : OK", test.name) 487 } 488 } 489 490 func TestIpToRange(t *testing.T) { 491 err := Init(nil) 492 require.NoError(t, err) 493 tests := []struct { 494 name string 495 env map[string]interface{} 496 code string 497 result string 498 err string 499 }{ 500 { 501 name: "IpToRange() test: IPv4", 502 env: map[string]interface{}{ 503 "ip": "192.168.1.1", 504 "netmask": "16", 505 }, 506 code: "IpToRange(ip, netmask)", 507 result: "192.168.0.0/16", 508 err: "", 509 }, 510 { 511 name: "IpToRange() test: IPv6", 512 env: map[string]interface{}{ 513 "ip": "2001:db8::1", 514 "netmask": "/64", 515 }, 516 code: "IpToRange(ip, netmask)", 517 result: "2001:db8::/64", 518 err: "", 519 }, 520 { 521 name: "IpToRange() test: malformed netmask", 522 env: map[string]interface{}{ 523 "ip": "192.168.0.1", 524 "netmask": "test", 525 }, 526 code: "IpToRange(ip, netmask)", 527 result: "", 528 err: "", 529 }, 530 { 531 name: "IpToRange() test: malformed IP", 532 env: map[string]interface{}{ 533 "ip": "a.b.c.d", 534 "netmask": "24", 535 }, 536 code: "IpToRange(ip, netmask)", 537 result: "", 538 err: "", 539 }, 540 { 541 name: "IpToRange() test: too high netmask", 542 env: map[string]interface{}{ 543 "ip": "192.168.1.1", 544 "netmask": "35", 545 }, 546 code: "IpToRange(ip, netmask)", 547 result: "", 548 err: "", 549 }, 550 } 551 552 for _, test := range tests { 553 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 554 require.NoError(t, err) 555 output, err := expr.Run(program, test.env) 556 require.NoError(t, err) 557 require.Equal(t, test.result, output) 558 log.Printf("test '%s' : OK", test.name) 559 } 560 } 561 562 func TestAtof(t *testing.T) { 563 err := Init(nil) 564 require.NoError(t, err) 565 566 tests := []struct { 567 name string 568 env map[string]interface{} 569 code string 570 result float64 571 }{ 572 { 573 name: "Atof() test: basic test", 574 env: map[string]interface{}{ 575 "testFloat": "1.5", 576 }, 577 code: "Atof(testFloat)", 578 result: 1.5, 579 }, 580 { 581 name: "Atof() test: bad float", 582 env: map[string]interface{}{ 583 "testFloat": "1aaa.5", 584 }, 585 code: "Atof(testFloat)", 586 result: 0.0, 587 }, 588 } 589 590 for _, test := range tests { 591 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 592 require.NoError(t, err) 593 output, err := expr.Run(program, test.env) 594 require.NoError(t, err) 595 require.InDelta(t, test.result, output, 0.000001) 596 } 597 } 598 599 func TestUpper(t *testing.T) { 600 testStr := "test" 601 expectedStr := "TEST" 602 603 env := map[string]interface{}{ 604 "testStr": testStr, 605 } 606 607 err := Init(nil) 608 require.NoError(t, err) 609 vm, err := expr.Compile("Upper(testStr)", GetExprOptions(env)...) 610 require.NoError(t, err) 611 612 out, err := expr.Run(vm, env) 613 614 require.NoError(t, err) 615 616 v, ok := out.(string) 617 if !ok { 618 t.Fatalf("Upper() should return a string") 619 } 620 621 if v != expectedStr { 622 t.Fatalf("Upper() should return test in upper case") 623 } 624 } 625 626 func TestTimeNow(t *testing.T) { 627 now, _ := TimeNow() 628 629 ti, err := time.Parse(time.RFC3339, now.(string)) 630 if err != nil { 631 t.Fatalf("Error parsing the return value of TimeNow: %s", err) 632 } 633 634 if -1*time.Until(ti) > time.Second { 635 t.Fatalf("TimeNow func should return time.Now().UTC()") 636 } 637 638 log.Printf("test 'TimeNow()' : OK") 639 } 640 641 func TestParseUri(t *testing.T) { 642 tests := []struct { 643 name string 644 env map[string]interface{} 645 code string 646 result map[string][]string 647 err string 648 }{ 649 { 650 name: "ParseUri() test: basic test", 651 env: map[string]interface{}{ 652 "uri": "/foo?a=1&b=2", 653 "ParseUri": ParseUri, 654 }, 655 code: "ParseUri(uri)", 656 result: map[string][]string{"a": {"1"}, "b": {"2"}}, 657 err: "", 658 }, 659 { 660 name: "ParseUri() test: no param", 661 env: map[string]interface{}{ 662 "uri": "/foo", 663 "ParseUri": ParseUri, 664 }, 665 code: "ParseUri(uri)", 666 result: map[string][]string{}, 667 err: "", 668 }, 669 { 670 name: "ParseUri() test: extra question mark", 671 env: map[string]interface{}{ 672 "uri": "/foo?a=1&b=2?", 673 "ParseUri": ParseUri, 674 }, 675 code: "ParseUri(uri)", 676 result: map[string][]string{"a": {"1"}, "b": {"2?"}}, 677 err: "", 678 }, 679 { 680 name: "ParseUri() test: weird params", 681 env: map[string]interface{}{ 682 "uri": "/foo?&?&&&&?=123", 683 "ParseUri": ParseUri, 684 }, 685 code: "ParseUri(uri)", 686 result: map[string][]string{"?": {"", "123"}}, 687 err: "", 688 }, 689 { 690 name: "ParseUri() test: bad encoding", 691 env: map[string]interface{}{ 692 "uri": "/foo?a=%%F", 693 "ParseUri": ParseUri, 694 }, 695 code: "ParseUri(uri)", 696 result: map[string][]string{}, 697 err: "", 698 }, 699 } 700 701 for _, test := range tests { 702 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 703 require.NoError(t, err) 704 output, err := expr.Run(program, test.env) 705 require.NoError(t, err) 706 require.Equal(t, test.result, output) 707 log.Printf("test '%s' : OK", test.name) 708 } 709 } 710 711 func TestQueryEscape(t *testing.T) { 712 tests := []struct { 713 name string 714 env map[string]interface{} 715 code string 716 result string 717 err string 718 }{ 719 { 720 name: "QueryEscape() test: basic test", 721 env: map[string]interface{}{ 722 "uri": "/foo?a=1&b=2", 723 "QueryEscape": QueryEscape, 724 }, 725 code: "QueryEscape(uri)", 726 result: "%2Ffoo%3Fa%3D1%26b%3D2", 727 err: "", 728 }, 729 { 730 name: "QueryEscape() test: basic test", 731 env: map[string]interface{}{ 732 "uri": "/foo?a=1&&b=<>'\"", 733 "QueryEscape": QueryEscape, 734 }, 735 code: "QueryEscape(uri)", 736 result: "%2Ffoo%3Fa%3D1%26%26b%3D%3C%3E%27%22", 737 err: "", 738 }, 739 } 740 741 for _, test := range tests { 742 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 743 require.NoError(t, err) 744 output, err := expr.Run(program, test.env) 745 require.NoError(t, err) 746 require.Equal(t, test.result, output) 747 log.Printf("test '%s' : OK", test.name) 748 } 749 } 750 751 func TestPathEscape(t *testing.T) { 752 tests := []struct { 753 name string 754 env map[string]interface{} 755 code string 756 result string 757 err string 758 }{ 759 { 760 name: "PathEscape() test: basic test", 761 env: map[string]interface{}{ 762 "uri": "/foo?a=1&b=2", 763 "PathEscape": PathEscape, 764 }, 765 code: "PathEscape(uri)", 766 result: "%2Ffoo%3Fa=1&b=2", 767 err: "", 768 }, 769 { 770 name: "PathEscape() test: basic test with more special chars", 771 env: map[string]interface{}{ 772 "uri": "/foo?a=1&&b=<>'\"", 773 "PathEscape": PathEscape, 774 }, 775 code: "PathEscape(uri)", 776 result: "%2Ffoo%3Fa=1&&b=%3C%3E%27%22", 777 err: "", 778 }, 779 } 780 781 for _, test := range tests { 782 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 783 require.NoError(t, err) 784 output, err := expr.Run(program, test.env) 785 require.NoError(t, err) 786 require.Equal(t, test.result, output) 787 log.Printf("test '%s' : OK", test.name) 788 } 789 } 790 791 func TestPathUnescape(t *testing.T) { 792 tests := []struct { 793 name string 794 env map[string]interface{} 795 code string 796 result string 797 err string 798 }{ 799 { 800 name: "PathUnescape() test: basic test", 801 env: map[string]interface{}{ 802 "uri": "%2Ffoo%3Fa=1&b=%3C%3E%27%22", 803 "PathUnescape": PathUnescape, 804 }, 805 code: "PathUnescape(uri)", 806 result: "/foo?a=1&b=<>'\"", 807 err: "", 808 }, 809 { 810 name: "PathUnescape() test: basic test with more special chars", 811 env: map[string]interface{}{ 812 "uri": "/$%7Bjndi", 813 "PathUnescape": PathUnescape, 814 }, 815 code: "PathUnescape(uri)", 816 result: "/${jndi", 817 err: "", 818 }, 819 } 820 821 for _, test := range tests { 822 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 823 require.NoError(t, err) 824 output, err := expr.Run(program, test.env) 825 require.NoError(t, err) 826 require.Equal(t, test.result, output) 827 log.Printf("test '%s' : OK", test.name) 828 } 829 } 830 831 func TestQueryUnescape(t *testing.T) { 832 tests := []struct { 833 name string 834 env map[string]interface{} 835 code string 836 result string 837 err string 838 }{ 839 { 840 name: "QueryUnescape() test: basic test", 841 env: map[string]interface{}{ 842 "uri": "%2Ffoo%3Fa=1&b=%3C%3E%27%22", 843 "QueryUnescape": QueryUnescape, 844 }, 845 code: "QueryUnescape(uri)", 846 result: "/foo?a=1&b=<>'\"", 847 err: "", 848 }, 849 { 850 name: "QueryUnescape() test: basic test with more special chars", 851 env: map[string]interface{}{ 852 "uri": "/$%7Bjndi", 853 "QueryUnescape": QueryUnescape, 854 }, 855 code: "QueryUnescape(uri)", 856 result: "/${jndi", 857 err: "", 858 }, 859 } 860 861 for _, test := range tests { 862 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 863 require.NoError(t, err) 864 output, err := expr.Run(program, test.env) 865 require.NoError(t, err) 866 require.Equal(t, test.result, output) 867 log.Printf("test '%s' : OK", test.name) 868 } 869 } 870 871 func TestLower(t *testing.T) { 872 tests := []struct { 873 name string 874 env map[string]interface{} 875 code string 876 result string 877 err string 878 }{ 879 { 880 name: "Lower() test: basic test", 881 env: map[string]interface{}{ 882 "name": "ABCDEFG", 883 "Lower": Lower, 884 }, 885 code: "Lower(name)", 886 result: "abcdefg", 887 err: "", 888 }, 889 { 890 name: "Lower() test: basic test with more special chars", 891 env: map[string]interface{}{ 892 "name": "AbcDefG!#", 893 "Lower": Lower, 894 }, 895 code: "Lower(name)", 896 result: "abcdefg!#", 897 err: "", 898 }, 899 } 900 901 for _, test := range tests { 902 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 903 require.NoError(t, err) 904 output, err := expr.Run(program, test.env) 905 require.NoError(t, err) 906 require.Equal(t, test.result, output) 907 log.Printf("test '%s' : OK", test.name) 908 } 909 } 910 911 func TestGetDecisionsCount(t *testing.T) { 912 existingIP := "1.2.3.4" 913 unknownIP := "1.2.3.5" 914 915 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP) 916 if err != nil { 917 t.Errorf("unable to convert '%s' to int: %s", existingIP, err) 918 } 919 920 // Add sample data to DB 921 dbClient = getDBClient(t) 922 923 decision := dbClient.Ent.Decision.Create(). 924 SetUntil(time.Now().Add(time.Hour)). 925 SetScenario("crowdsec/test"). 926 SetStartIP(start_ip). 927 SetStartSuffix(start_sfx). 928 SetEndIP(end_ip). 929 SetEndSuffix(end_sfx). 930 SetIPSize(int64(ip_sz)). 931 SetType("ban"). 932 SetScope("IP"). 933 SetValue(existingIP). 934 SetOrigin("CAPI"). 935 SaveX(context.Background()) 936 937 if decision == nil { 938 require.Error(t, errors.Errorf("Failed to create sample decision")) 939 } 940 941 err = Init(dbClient) 942 require.NoError(t, err) 943 944 tests := []struct { 945 name string 946 env map[string]interface{} 947 code string 948 result string 949 err string 950 }{ 951 { 952 name: "GetDecisionsCount() test: existing IP count", 953 env: map[string]interface{}{ 954 "Alert": &models.Alert{ 955 Source: &models.Source{ 956 Value: &existingIP, 957 }, 958 Decisions: []*models.Decision{ 959 { 960 Value: &existingIP, 961 }, 962 }, 963 }, 964 }, 965 code: "Sprintf('%d', GetDecisionsCount(Alert.GetValue()))", 966 result: "1", 967 err: "", 968 }, 969 { 970 name: "GetDecisionsCount() test: unknown IP count", 971 env: map[string]interface{}{ 972 "Alert": &models.Alert{ 973 Source: &models.Source{ 974 Value: &unknownIP, 975 }, 976 Decisions: []*models.Decision{ 977 { 978 Value: &unknownIP, 979 }, 980 }, 981 }, 982 }, 983 code: "Sprintf('%d', GetDecisionsCount(Alert.GetValue()))", 984 result: "0", 985 err: "", 986 }, 987 } 988 989 for _, test := range tests { 990 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 991 require.NoError(t, err) 992 output, err := expr.Run(program, test.env) 993 require.NoError(t, err) 994 require.Equal(t, test.result, output) 995 log.Printf("test '%s' : OK", test.name) 996 } 997 } 998 func TestGetDecisionsSinceCount(t *testing.T) { 999 existingIP := "1.2.3.4" 1000 unknownIP := "1.2.3.5" 1001 1002 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP) 1003 if err != nil { 1004 t.Errorf("unable to convert '%s' to int: %s", existingIP, err) 1005 } 1006 // Add sample data to DB 1007 dbClient = getDBClient(t) 1008 1009 decision := dbClient.Ent.Decision.Create(). 1010 SetUntil(time.Now().Add(time.Hour)). 1011 SetScenario("crowdsec/test"). 1012 SetStartIP(start_ip). 1013 SetStartSuffix(start_sfx). 1014 SetEndIP(end_ip). 1015 SetEndSuffix(end_sfx). 1016 SetIPSize(int64(ip_sz)). 1017 SetType("ban"). 1018 SetScope("IP"). 1019 SetValue(existingIP). 1020 SetOrigin("CAPI"). 1021 SaveX(context.Background()) 1022 if decision == nil { 1023 require.Error(t, errors.Errorf("Failed to create sample decision")) 1024 } 1025 1026 decision2 := dbClient.Ent.Decision.Create(). 1027 SetCreatedAt(time.Now().AddDate(0, 0, -1)). 1028 SetUntil(time.Now().AddDate(0, 0, -1)). 1029 SetScenario("crowdsec/test"). 1030 SetStartIP(start_ip). 1031 SetStartSuffix(start_sfx). 1032 SetEndIP(end_ip). 1033 SetEndSuffix(end_sfx). 1034 SetIPSize(int64(ip_sz)). 1035 SetType("ban"). 1036 SetScope("IP"). 1037 SetValue(existingIP). 1038 SetOrigin("CAPI"). 1039 SaveX(context.Background()) 1040 1041 if decision2 == nil { 1042 require.Error(t, errors.Errorf("Failed to create sample decision")) 1043 } 1044 1045 err = Init(dbClient) 1046 require.NoError(t, err) 1047 1048 tests := []struct { 1049 name string 1050 env map[string]interface{} 1051 code string 1052 result string 1053 err string 1054 }{ 1055 { 1056 name: "GetDecisionsSinceCount() test: existing IP count since more than 1 day", 1057 env: map[string]interface{}{ 1058 "Alert": &models.Alert{ 1059 Source: &models.Source{ 1060 Value: &existingIP, 1061 }, 1062 Decisions: []*models.Decision{ 1063 { 1064 Value: &existingIP, 1065 }, 1066 }, 1067 }, 1068 }, 1069 code: "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '25h'))", 1070 result: "2", 1071 err: "", 1072 }, 1073 { 1074 name: "GetDecisionsSinceCount() test: existing IP count since more than 1 hour", 1075 env: map[string]interface{}{ 1076 "Alert": &models.Alert{ 1077 Source: &models.Source{ 1078 Value: &existingIP, 1079 }, 1080 Decisions: []*models.Decision{ 1081 { 1082 Value: &existingIP, 1083 }, 1084 }, 1085 }, 1086 }, 1087 code: "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '1h'))", 1088 result: "1", 1089 err: "", 1090 }, 1091 { 1092 name: "GetDecisionsSinceCount() test: unknown IP count", 1093 env: map[string]interface{}{ 1094 "Alert": &models.Alert{ 1095 Source: &models.Source{ 1096 Value: &unknownIP, 1097 }, 1098 Decisions: []*models.Decision{ 1099 { 1100 Value: &unknownIP, 1101 }, 1102 }, 1103 }, 1104 }, 1105 code: "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '1h'))", 1106 result: "0", 1107 err: "", 1108 }, 1109 } 1110 1111 for _, test := range tests { 1112 program, err := expr.Compile(test.code, GetExprOptions(test.env)...) 1113 require.NoError(t, err) 1114 output, err := expr.Run(program, test.env) 1115 require.NoError(t, err) 1116 require.Equal(t, test.result, output) 1117 log.Printf("test '%s' : OK", test.name) 1118 } 1119 } 1120 1121 func TestParseUnixTime(t *testing.T) { 1122 tests := []struct { 1123 name string 1124 value string 1125 expected time.Time 1126 expectedErr string 1127 }{ 1128 { 1129 name: "ParseUnix() test: valid value with milli", 1130 value: "1672239773.3590894", 1131 expected: time.Date(2022, 12, 28, 15, 02, 53, 0, time.UTC), 1132 }, 1133 { 1134 name: "ParseUnix() test: valid value without milli", 1135 value: "1672239773", 1136 expected: time.Date(2022, 12, 28, 15, 02, 53, 0, time.UTC), 1137 }, 1138 { 1139 name: "ParseUnix() test: invalid input", 1140 value: "AbcDefG!#", 1141 expected: time.Time{}, 1142 expectedErr: "unable to parse AbcDefG!# as unix timestamp", 1143 }, 1144 { 1145 name: "ParseUnix() test: negative value", 1146 value: "-1000", 1147 expected: time.Time{}, 1148 expectedErr: "unable to parse -1000 as unix timestamp", 1149 }, 1150 } 1151 1152 for _, tc := range tests { 1153 tc := tc 1154 t.Run(tc.name, func(t *testing.T) { 1155 output, err := ParseUnixTime(tc.value) 1156 cstest.RequireErrorContains(t, err, tc.expectedErr) 1157 if tc.expectedErr != "" { 1158 return 1159 } 1160 require.WithinDuration(t, tc.expected, output.(time.Time), time.Second) 1161 }) 1162 } 1163 } 1164 1165 func TestIsIp(t *testing.T) { 1166 if err := Init(nil); err != nil { 1167 log.Fatal(err) 1168 } 1169 1170 tests := []struct { 1171 name string 1172 expr string 1173 value string 1174 expected bool 1175 expectedBuildErr bool 1176 }{ 1177 { 1178 name: "IsIPV4() test: valid IPv4", 1179 expr: `IsIPV4(value)`, 1180 value: "1.2.3.4", 1181 expected: true, 1182 }, 1183 { 1184 name: "IsIPV6() test: valid IPv6", 1185 expr: `IsIPV6(value)`, 1186 value: "1.2.3.4", 1187 expected: false, 1188 }, 1189 { 1190 name: "IsIPV6() test: valid IPv6", 1191 expr: `IsIPV6(value)`, 1192 value: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 1193 expected: true, 1194 }, 1195 { 1196 name: "IsIPV4() test: valid IPv6", 1197 expr: `IsIPV4(value)`, 1198 value: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 1199 expected: false, 1200 }, 1201 { 1202 name: "IsIP() test: invalid IP", 1203 expr: `IsIP(value)`, 1204 value: "foo.bar", 1205 expected: false, 1206 }, 1207 { 1208 name: "IsIP() test: valid IPv4", 1209 expr: `IsIP(value)`, 1210 value: "1.2.3.4", 1211 expected: true, 1212 }, 1213 { 1214 name: "IsIP() test: valid IPv6", 1215 expr: `IsIP(value)`, 1216 value: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 1217 expected: true, 1218 }, 1219 { 1220 name: "IsIPV4() test: invalid IPv4", 1221 expr: `IsIPV4(value)`, 1222 value: "foo.bar", 1223 expected: false, 1224 }, 1225 { 1226 name: "IsIPV6() test: invalid IPv6", 1227 expr: `IsIPV6(value)`, 1228 value: "foo.bar", 1229 expected: false, 1230 }, 1231 { 1232 name: "IsIPV4() test: invalid type", 1233 expr: `IsIPV4(42)`, 1234 value: "", 1235 expected: false, 1236 expectedBuildErr: true, 1237 }, 1238 { 1239 name: "IsIP() test: invalid type", 1240 expr: `IsIP(42)`, 1241 value: "", 1242 expected: false, 1243 expectedBuildErr: true, 1244 }, 1245 { 1246 name: "IsIPV6() test: invalid type", 1247 expr: `IsIPV6(42)`, 1248 value: "", 1249 expected: false, 1250 expectedBuildErr: true, 1251 }, 1252 } 1253 1254 for _, tc := range tests { 1255 tc := tc 1256 t.Run(tc.name, func(t *testing.T) { 1257 vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...) 1258 if tc.expectedBuildErr { 1259 require.Error(t, err) 1260 return 1261 } 1262 require.NoError(t, err) 1263 output, err := expr.Run(vm, map[string]interface{}{"value": tc.value}) 1264 require.NoError(t, err) 1265 assert.IsType(t, tc.expected, output) 1266 assert.Equal(t, tc.expected, output.(bool)) 1267 }) 1268 } 1269 } 1270 1271 func TestToString(t *testing.T) { 1272 err := Init(nil) 1273 require.NoError(t, err) 1274 1275 tests := []struct { 1276 name string 1277 value interface{} 1278 expected string 1279 expr string 1280 }{ 1281 { 1282 name: "ToString() test: valid string", 1283 value: "foo", 1284 expected: "foo", 1285 expr: `ToString(value)`, 1286 }, 1287 { 1288 name: "ToString() test: valid string", 1289 value: interface{}("foo"), 1290 expected: "foo", 1291 expr: `ToString(value)`, 1292 }, 1293 { 1294 name: "ToString() test: invalid type", 1295 value: 1, 1296 expected: "", 1297 expr: `ToString(value)`, 1298 }, 1299 { 1300 name: "ToString() test: invalid type 2", 1301 value: interface{}(nil), 1302 expected: "", 1303 expr: `ToString(value)`, 1304 }, 1305 } 1306 for _, tc := range tests { 1307 tc := tc 1308 t.Run(tc.name, func(t *testing.T) { 1309 vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...) 1310 require.NoError(t, err) 1311 output, err := expr.Run(vm, map[string]interface{}{"value": tc.value}) 1312 require.NoError(t, err) 1313 require.Equal(t, tc.expected, output) 1314 }) 1315 } 1316 } 1317 1318 func TestB64Decode(t *testing.T) { 1319 err := Init(nil) 1320 require.NoError(t, err) 1321 1322 tests := []struct { 1323 name string 1324 value interface{} 1325 expected string 1326 expr string 1327 expectedBuildErr bool 1328 expectedRuntimeErr bool 1329 }{ 1330 { 1331 name: "B64Decode() test: valid string", 1332 value: "Zm9v", 1333 expected: "foo", 1334 expr: `B64Decode(value)`, 1335 expectedBuildErr: false, 1336 }, 1337 { 1338 name: "B64Decode() test: invalid string", 1339 value: "foo", 1340 expected: "", 1341 expr: `B64Decode(value)`, 1342 expectedBuildErr: false, 1343 expectedRuntimeErr: true, 1344 }, 1345 { 1346 name: "B64Decode() test: invalid type", 1347 value: 1, 1348 expected: "", 1349 expr: `B64Decode(value)`, 1350 expectedBuildErr: true, 1351 }, 1352 } 1353 for _, tc := range tests { 1354 tc := tc 1355 t.Run(tc.name, func(t *testing.T) { 1356 vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...) 1357 if tc.expectedBuildErr { 1358 require.Error(t, err) 1359 return 1360 } 1361 require.NoError(t, err) 1362 output, err := expr.Run(vm, map[string]interface{}{"value": tc.value}) 1363 if tc.expectedRuntimeErr { 1364 require.Error(t, err) 1365 return 1366 } 1367 require.NoError(t, err) 1368 require.Equal(t, tc.expected, output) 1369 }) 1370 } 1371 } 1372 1373 func TestParseKv(t *testing.T) { 1374 err := Init(nil) 1375 require.NoError(t, err) 1376 1377 tests := []struct { 1378 name string 1379 value string 1380 expected map[string]string 1381 expr string 1382 expectedBuildErr bool 1383 expectedRuntimeErr bool 1384 }{ 1385 { 1386 name: "ParseKv() test: valid string", 1387 value: "foo=bar", 1388 expected: map[string]string{"foo": "bar"}, 1389 expr: `ParseKV(value, out, "a")`, 1390 }, 1391 { 1392 name: "ParseKv() test: valid string", 1393 value: "foo=bar bar=foo", 1394 expected: map[string]string{"foo": "bar", "bar": "foo"}, 1395 expr: `ParseKV(value, out, "a")`, 1396 }, 1397 { 1398 name: "ParseKv() test: valid string", 1399 value: "foo=bar bar=foo foo=foo", 1400 expected: map[string]string{"foo": "foo", "bar": "foo"}, 1401 expr: `ParseKV(value, out, "a")`, 1402 }, 1403 { 1404 name: "ParseKV() test: quoted string", 1405 value: `foo="bar=toto"`, 1406 expected: map[string]string{"foo": "bar=toto"}, 1407 expr: `ParseKV(value, out, "a")`, 1408 }, 1409 { 1410 name: "ParseKV() test: empty unquoted string", 1411 value: `foo= bar=toto`, 1412 expected: map[string]string{"bar": "toto", "foo": ""}, 1413 expr: `ParseKV(value, out, "a")`, 1414 }, 1415 { 1416 name: "ParseKV() test: empty quoted string ", 1417 value: `foo="" bar=toto`, 1418 expected: map[string]string{"bar": "toto", "foo": ""}, 1419 expr: `ParseKV(value, out, "a")`, 1420 }, 1421 } 1422 1423 for _, tc := range tests { 1424 tc := tc 1425 t.Run(tc.name, func(t *testing.T) { 1426 outMap := make(map[string]interface{}) 1427 env := map[string]interface{}{ 1428 "value": tc.value, 1429 "out": outMap, 1430 } 1431 vm, err := expr.Compile(tc.expr, GetExprOptions(env)...) 1432 require.NoError(t, err) 1433 _, err = expr.Run(vm, env) 1434 require.NoError(t, err) 1435 assert.Equal(t, tc.expected, outMap["a"]) 1436 }) 1437 } 1438 }