github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/appsec/appsec_hooks_test.go (about) 1 package appsecacquisition 2 3 import ( 4 "net/http" 5 "net/url" 6 "testing" 7 8 "github.com/crowdsecurity/crowdsec/pkg/appsec" 9 "github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule" 10 "github.com/crowdsecurity/crowdsec/pkg/types" 11 "github.com/davecgh/go-spew/spew" 12 log "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestAppsecOnMatchHooks(t *testing.T) { 17 tests := []appsecRuleTest{ 18 { 19 name: "no rule : check return code", 20 expected_load_ok: true, 21 inband_rules: []appsec_rule.CustomRule{ 22 { 23 Name: "rule1", 24 Zones: []string{"ARGS"}, 25 Variables: []string{"foo"}, 26 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 27 Transform: []string{"lowercase"}, 28 }, 29 }, 30 input_request: appsec.ParsedRequest{ 31 RemoteAddr: "1.2.3.4", 32 Method: "GET", 33 URI: "/urllll", 34 Args: url.Values{"foo": []string{"toto"}}, 35 }, 36 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 37 require.Len(t, events, 2) 38 require.Equal(t, types.APPSEC, events[0].Type) 39 require.Equal(t, types.LOG, events[1].Type) 40 require.Len(t, responses, 1) 41 require.Equal(t, 403, responses[0].BouncerHTTPResponseCode) 42 require.Equal(t, 403, responses[0].UserHTTPResponseCode) 43 require.Equal(t, appsec.BanRemediation, responses[0].Action) 44 45 }, 46 }, 47 { 48 name: "on_match: change return code", 49 expected_load_ok: true, 50 inband_rules: []appsec_rule.CustomRule{ 51 { 52 Name: "rule1", 53 Zones: []string{"ARGS"}, 54 Variables: []string{"foo"}, 55 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 56 Transform: []string{"lowercase"}, 57 }, 58 }, 59 on_match: []appsec.Hook{ 60 {Filter: "IsInBand == true", Apply: []string{"SetReturnCode(413)"}}, 61 }, 62 input_request: appsec.ParsedRequest{ 63 RemoteAddr: "1.2.3.4", 64 Method: "GET", 65 URI: "/urllll", 66 Args: url.Values{"foo": []string{"toto"}}, 67 }, 68 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 69 require.Len(t, events, 2) 70 require.Equal(t, types.APPSEC, events[0].Type) 71 require.Equal(t, types.LOG, events[1].Type) 72 require.Len(t, responses, 1) 73 require.Equal(t, 403, responses[0].BouncerHTTPResponseCode) 74 require.Equal(t, 413, responses[0].UserHTTPResponseCode) 75 require.Equal(t, appsec.BanRemediation, responses[0].Action) 76 }, 77 }, 78 { 79 name: "on_match: change action to a non standard one (log)", 80 expected_load_ok: true, 81 inband_rules: []appsec_rule.CustomRule{ 82 { 83 Name: "rule1", 84 Zones: []string{"ARGS"}, 85 Variables: []string{"foo"}, 86 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 87 Transform: []string{"lowercase"}, 88 }, 89 }, 90 on_match: []appsec.Hook{ 91 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('log')"}}, 92 }, 93 input_request: appsec.ParsedRequest{ 94 RemoteAddr: "1.2.3.4", 95 Method: "GET", 96 URI: "/urllll", 97 Args: url.Values{"foo": []string{"toto"}}, 98 }, 99 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 100 require.Len(t, events, 2) 101 require.Equal(t, types.APPSEC, events[0].Type) 102 require.Equal(t, types.LOG, events[1].Type) 103 require.Len(t, responses, 1) 104 require.Equal(t, "log", responses[0].Action) 105 require.Equal(t, 403, responses[0].BouncerHTTPResponseCode) 106 require.Equal(t, 403, responses[0].UserHTTPResponseCode) 107 }, 108 }, 109 { 110 name: "on_match: change action to another standard one (allow)", 111 expected_load_ok: true, 112 inband_rules: []appsec_rule.CustomRule{ 113 { 114 Name: "rule1", 115 Zones: []string{"ARGS"}, 116 Variables: []string{"foo"}, 117 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 118 Transform: []string{"lowercase"}, 119 }, 120 }, 121 on_match: []appsec.Hook{ 122 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}}, 123 }, 124 input_request: appsec.ParsedRequest{ 125 RemoteAddr: "1.2.3.4", 126 Method: "GET", 127 URI: "/urllll", 128 Args: url.Values{"foo": []string{"toto"}}, 129 }, 130 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 131 require.Len(t, events, 2) 132 require.Equal(t, types.APPSEC, events[0].Type) 133 require.Equal(t, types.LOG, events[1].Type) 134 require.Len(t, responses, 1) 135 require.Equal(t, appsec.AllowRemediation, responses[0].Action) 136 }, 137 }, 138 { 139 name: "on_match: change action to another standard one (ban)", 140 expected_load_ok: true, 141 inband_rules: []appsec_rule.CustomRule{ 142 { 143 Name: "rule1", 144 Zones: []string{"ARGS"}, 145 Variables: []string{"foo"}, 146 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 147 Transform: []string{"lowercase"}, 148 }, 149 }, 150 on_match: []appsec.Hook{ 151 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('ban')"}}, 152 }, 153 input_request: appsec.ParsedRequest{ 154 RemoteAddr: "1.2.3.4", 155 Method: "GET", 156 URI: "/urllll", 157 Args: url.Values{"foo": []string{"toto"}}, 158 }, 159 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 160 require.Len(t, responses, 1) 161 //note: SetAction normalizes deny, ban and block to ban 162 require.Equal(t, appsec.BanRemediation, responses[0].Action) 163 }, 164 }, 165 { 166 name: "on_match: change action to another standard one (captcha)", 167 expected_load_ok: true, 168 inband_rules: []appsec_rule.CustomRule{ 169 { 170 Name: "rule1", 171 Zones: []string{"ARGS"}, 172 Variables: []string{"foo"}, 173 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 174 Transform: []string{"lowercase"}, 175 }, 176 }, 177 on_match: []appsec.Hook{ 178 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')"}}, 179 }, 180 input_request: appsec.ParsedRequest{ 181 RemoteAddr: "1.2.3.4", 182 Method: "GET", 183 URI: "/urllll", 184 Args: url.Values{"foo": []string{"toto"}}, 185 }, 186 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 187 require.Len(t, responses, 1) 188 //note: SetAction normalizes deny, ban and block to ban 189 require.Equal(t, appsec.CaptchaRemediation, responses[0].Action) 190 }, 191 }, 192 { 193 name: "on_match: change action to a non standard one", 194 expected_load_ok: true, 195 inband_rules: []appsec_rule.CustomRule{ 196 { 197 Name: "rule1", 198 Zones: []string{"ARGS"}, 199 Variables: []string{"foo"}, 200 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 201 Transform: []string{"lowercase"}, 202 }, 203 }, 204 on_match: []appsec.Hook{ 205 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('foobar')"}}, 206 }, 207 input_request: appsec.ParsedRequest{ 208 RemoteAddr: "1.2.3.4", 209 Method: "GET", 210 URI: "/urllll", 211 Args: url.Values{"foo": []string{"toto"}}, 212 }, 213 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 214 require.Len(t, events, 2) 215 require.Equal(t, types.APPSEC, events[0].Type) 216 require.Equal(t, types.LOG, events[1].Type) 217 require.Len(t, responses, 1) 218 require.Equal(t, "foobar", responses[0].Action) 219 }, 220 }, 221 { 222 name: "on_match: cancel alert", 223 expected_load_ok: true, 224 inband_rules: []appsec_rule.CustomRule{ 225 { 226 Name: "rule42", 227 Zones: []string{"ARGS"}, 228 Variables: []string{"foo"}, 229 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 230 Transform: []string{"lowercase"}, 231 }, 232 }, 233 on_match: []appsec.Hook{ 234 {Filter: "IsInBand == true && LogInfo('XX -> %s', evt.Appsec.MatchedRules.GetName())", Apply: []string{"CancelAlert()"}}, 235 }, 236 input_request: appsec.ParsedRequest{ 237 RemoteAddr: "1.2.3.4", 238 Method: "GET", 239 URI: "/urllll", 240 Args: url.Values{"foo": []string{"toto"}}, 241 }, 242 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 243 require.Len(t, events, 1) 244 require.Equal(t, types.LOG, events[0].Type) 245 require.Len(t, responses, 1) 246 require.Equal(t, appsec.BanRemediation, responses[0].Action) 247 }, 248 }, 249 { 250 name: "on_match: cancel event", 251 expected_load_ok: true, 252 inband_rules: []appsec_rule.CustomRule{ 253 { 254 Name: "rule42", 255 Zones: []string{"ARGS"}, 256 Variables: []string{"foo"}, 257 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 258 Transform: []string{"lowercase"}, 259 }, 260 }, 261 on_match: []appsec.Hook{ 262 {Filter: "IsInBand == true", Apply: []string{"CancelEvent()"}}, 263 }, 264 input_request: appsec.ParsedRequest{ 265 RemoteAddr: "1.2.3.4", 266 Method: "GET", 267 URI: "/urllll", 268 Args: url.Values{"foo": []string{"toto"}}, 269 }, 270 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 271 require.Len(t, events, 1) 272 require.Equal(t, types.APPSEC, events[0].Type) 273 require.Len(t, responses, 1) 274 require.Equal(t, appsec.BanRemediation, responses[0].Action) 275 }, 276 }, 277 } 278 for _, test := range tests { 279 t.Run(test.name, func(t *testing.T) { 280 loadAppSecEngine(test, t) 281 }) 282 } 283 } 284 285 func TestAppsecPreEvalHooks(t *testing.T) { 286 287 tests := []appsecRuleTest{ 288 { 289 name: "Basic on_load hook to disable inband rule", 290 expected_load_ok: true, 291 inband_rules: []appsec_rule.CustomRule{ 292 { 293 Name: "rule1", 294 Zones: []string{"ARGS"}, 295 Variables: []string{"foo"}, 296 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 297 Transform: []string{"lowercase"}, 298 }, 299 }, 300 pre_eval: []appsec.Hook{ 301 {Filter: "1 == 1", Apply: []string{"RemoveInBandRuleByName('rule1')"}}, 302 }, 303 input_request: appsec.ParsedRequest{ 304 RemoteAddr: "1.2.3.4", 305 Method: "GET", 306 URI: "/urllll", 307 Args: url.Values{"foo": []string{"toto"}}, 308 }, 309 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 310 require.Empty(t, events) 311 require.Len(t, responses, 1) 312 require.False(t, responses[0].InBandInterrupt) 313 require.False(t, responses[0].OutOfBandInterrupt) 314 }, 315 }, 316 { 317 name: "Basic on_load fails to disable rule", 318 expected_load_ok: true, 319 inband_rules: []appsec_rule.CustomRule{ 320 { 321 Name: "rule1", 322 Zones: []string{"ARGS"}, 323 Variables: []string{"foo"}, 324 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 325 Transform: []string{"lowercase"}, 326 }, 327 }, 328 pre_eval: []appsec.Hook{ 329 {Filter: "1 ==2", Apply: []string{"RemoveInBandRuleByName('rule1')"}}, 330 }, 331 input_request: appsec.ParsedRequest{ 332 RemoteAddr: "1.2.3.4", 333 Method: "GET", 334 URI: "/urllll", 335 Args: url.Values{"foo": []string{"toto"}}, 336 }, 337 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 338 require.Len(t, events, 2) 339 require.Equal(t, types.APPSEC, events[0].Type) 340 341 require.Equal(t, types.LOG, events[1].Type) 342 require.True(t, events[1].Appsec.HasInBandMatches) 343 require.Len(t, events[1].Appsec.MatchedRules, 1) 344 require.Equal(t, "rule1", events[1].Appsec.MatchedRules[0]["msg"]) 345 346 require.Len(t, responses, 1) 347 require.True(t, responses[0].InBandInterrupt) 348 349 }, 350 }, 351 { 352 name: "on_load : disable inband by tag", 353 expected_load_ok: true, 354 inband_rules: []appsec_rule.CustomRule{ 355 { 356 Name: "rulez", 357 Zones: []string{"ARGS"}, 358 Variables: []string{"foo"}, 359 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 360 Transform: []string{"lowercase"}, 361 }, 362 }, 363 pre_eval: []appsec.Hook{ 364 {Apply: []string{"RemoveInBandRuleByTag('crowdsec-rulez')"}}, 365 }, 366 input_request: appsec.ParsedRequest{ 367 RemoteAddr: "1.2.3.4", 368 Method: "GET", 369 URI: "/urllll", 370 Args: url.Values{"foo": []string{"toto"}}, 371 }, 372 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 373 require.Empty(t, events) 374 require.Len(t, responses, 1) 375 require.False(t, responses[0].InBandInterrupt) 376 require.False(t, responses[0].OutOfBandInterrupt) 377 }, 378 }, 379 { 380 name: "on_load : disable inband by ID", 381 expected_load_ok: true, 382 inband_rules: []appsec_rule.CustomRule{ 383 { 384 Name: "rulez", 385 Zones: []string{"ARGS"}, 386 Variables: []string{"foo"}, 387 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 388 Transform: []string{"lowercase"}, 389 }, 390 }, 391 pre_eval: []appsec.Hook{ 392 {Apply: []string{"RemoveInBandRuleByID(1516470898)"}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 393 }, 394 input_request: appsec.ParsedRequest{ 395 RemoteAddr: "1.2.3.4", 396 Method: "GET", 397 URI: "/urllll", 398 Args: url.Values{"foo": []string{"toto"}}, 399 }, 400 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 401 require.Empty(t, events) 402 require.Len(t, responses, 1) 403 require.False(t, responses[0].InBandInterrupt) 404 require.False(t, responses[0].OutOfBandInterrupt) 405 }, 406 }, 407 { 408 name: "on_load : disable inband by name", 409 expected_load_ok: true, 410 inband_rules: []appsec_rule.CustomRule{ 411 { 412 Name: "rulez", 413 Zones: []string{"ARGS"}, 414 Variables: []string{"foo"}, 415 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 416 Transform: []string{"lowercase"}, 417 }, 418 }, 419 pre_eval: []appsec.Hook{ 420 {Apply: []string{"RemoveInBandRuleByName('rulez')"}}, 421 }, 422 input_request: appsec.ParsedRequest{ 423 RemoteAddr: "1.2.3.4", 424 Method: "GET", 425 URI: "/urllll", 426 Args: url.Values{"foo": []string{"toto"}}, 427 }, 428 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 429 require.Empty(t, events) 430 require.Len(t, responses, 1) 431 require.False(t, responses[0].InBandInterrupt) 432 require.False(t, responses[0].OutOfBandInterrupt) 433 }, 434 }, 435 { 436 name: "on_load : outofband default behavior", 437 expected_load_ok: true, 438 outofband_rules: []appsec_rule.CustomRule{ 439 { 440 Name: "rulez", 441 Zones: []string{"ARGS"}, 442 Variables: []string{"foo"}, 443 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 444 Transform: []string{"lowercase"}, 445 }, 446 }, 447 input_request: appsec.ParsedRequest{ 448 RemoteAddr: "1.2.3.4", 449 Method: "GET", 450 URI: "/urllll", 451 Args: url.Values{"foo": []string{"toto"}}, 452 }, 453 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 454 require.Len(t, events, 1) 455 require.Equal(t, types.LOG, events[0].Type) 456 require.True(t, events[0].Appsec.HasOutBandMatches) 457 require.False(t, events[0].Appsec.HasInBandMatches) 458 require.Len(t, events[0].Appsec.MatchedRules, 1) 459 require.Equal(t, "rulez", events[0].Appsec.MatchedRules[0]["msg"]) 460 //maybe surprising, but response won't mention OOB event, as it's sent as soon as the inband phase is over. 461 require.Len(t, responses, 1) 462 require.False(t, responses[0].InBandInterrupt) 463 require.False(t, responses[0].OutOfBandInterrupt) 464 }, 465 }, 466 { 467 name: "on_load : set remediation by tag", 468 expected_load_ok: true, 469 inband_rules: []appsec_rule.CustomRule{ 470 { 471 Name: "rulez", 472 Zones: []string{"ARGS"}, 473 Variables: []string{"foo"}, 474 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 475 Transform: []string{"lowercase"}, 476 }, 477 }, 478 pre_eval: []appsec.Hook{ 479 {Apply: []string{"SetRemediationByTag('crowdsec-rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 480 }, 481 input_request: appsec.ParsedRequest{ 482 RemoteAddr: "1.2.3.4", 483 Method: "GET", 484 URI: "/urllll", 485 Args: url.Values{"foo": []string{"toto"}}, 486 }, 487 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 488 require.Len(t, events, 2) 489 require.Len(t, responses, 1) 490 require.Equal(t, "foobar", responses[0].Action) 491 }, 492 }, 493 { 494 name: "on_load : set remediation by name", 495 expected_load_ok: true, 496 inband_rules: []appsec_rule.CustomRule{ 497 { 498 Name: "rulez", 499 Zones: []string{"ARGS"}, 500 Variables: []string{"foo"}, 501 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 502 Transform: []string{"lowercase"}, 503 }, 504 }, 505 pre_eval: []appsec.Hook{ 506 {Apply: []string{"SetRemediationByName('rulez', 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 507 }, 508 input_request: appsec.ParsedRequest{ 509 RemoteAddr: "1.2.3.4", 510 Method: "GET", 511 URI: "/urllll", 512 Args: url.Values{"foo": []string{"toto"}}, 513 }, 514 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 515 require.Len(t, events, 2) 516 require.Len(t, responses, 1) 517 require.Equal(t, "foobar", responses[0].Action) 518 }, 519 }, 520 { 521 name: "on_load : set remediation by ID", 522 expected_load_ok: true, 523 inband_rules: []appsec_rule.CustomRule{ 524 { 525 Name: "rulez", 526 Zones: []string{"ARGS"}, 527 Variables: []string{"foo"}, 528 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 529 Transform: []string{"lowercase"}, 530 }, 531 }, 532 pre_eval: []appsec.Hook{ 533 {Apply: []string{"SetRemediationByID(1516470898, 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 534 }, 535 input_request: appsec.ParsedRequest{ 536 RemoteAddr: "1.2.3.4", 537 Method: "GET", 538 URI: "/urllll", 539 Args: url.Values{"foo": []string{"toto"}}, 540 }, 541 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 542 require.Len(t, events, 2) 543 require.Len(t, responses, 1) 544 require.Equal(t, "foobar", responses[0].Action) 545 require.Equal(t, "foobar", appsecResponse.Action) 546 require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus) 547 }, 548 }, 549 } 550 551 for _, test := range tests { 552 t.Run(test.name, func(t *testing.T) { 553 loadAppSecEngine(test, t) 554 }) 555 } 556 } 557 558 func TestAppsecRemediationConfigHooks(t *testing.T) { 559 560 tests := []appsecRuleTest{ 561 { 562 name: "Basic matching rule", 563 expected_load_ok: true, 564 inband_rules: []appsec_rule.CustomRule{ 565 { 566 Name: "rule1", 567 Zones: []string{"ARGS"}, 568 Variables: []string{"foo"}, 569 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 570 Transform: []string{"lowercase"}, 571 }, 572 }, 573 input_request: appsec.ParsedRequest{ 574 RemoteAddr: "1.2.3.4", 575 Method: "GET", 576 URI: "/urllll", 577 Args: url.Values{"foo": []string{"toto"}}, 578 }, 579 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 580 require.Equal(t, appsec.BanRemediation, responses[0].Action) 581 require.Equal(t, http.StatusForbidden, statusCode) 582 require.Equal(t, appsec.BanRemediation, appsecResponse.Action) 583 require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus) 584 }, 585 }, 586 { 587 name: "SetRemediation", 588 expected_load_ok: true, 589 inband_rules: []appsec_rule.CustomRule{ 590 { 591 Name: "rule1", 592 Zones: []string{"ARGS"}, 593 Variables: []string{"foo"}, 594 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 595 Transform: []string{"lowercase"}, 596 }, 597 }, 598 input_request: appsec.ParsedRequest{ 599 RemoteAddr: "1.2.3.4", 600 Method: "GET", 601 URI: "/urllll", 602 Args: url.Values{"foo": []string{"toto"}}, 603 }, 604 on_match: []appsec.Hook{{Apply: []string{"SetRemediation('captcha')"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 605 606 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 607 require.Equal(t, appsec.CaptchaRemediation, responses[0].Action) 608 require.Equal(t, http.StatusForbidden, statusCode) 609 require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action) 610 require.Equal(t, http.StatusForbidden, appsecResponse.HTTPStatus) 611 }, 612 }, 613 { 614 name: "SetRemediation", 615 expected_load_ok: true, 616 inband_rules: []appsec_rule.CustomRule{ 617 { 618 Name: "rule1", 619 Zones: []string{"ARGS"}, 620 Variables: []string{"foo"}, 621 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 622 Transform: []string{"lowercase"}, 623 }, 624 }, 625 input_request: appsec.ParsedRequest{ 626 RemoteAddr: "1.2.3.4", 627 Method: "GET", 628 URI: "/urllll", 629 Args: url.Values{"foo": []string{"toto"}}, 630 }, 631 on_match: []appsec.Hook{{Apply: []string{"SetReturnCode(418)"}}}, //rule ID is generated at runtime. If you change rule, it will break the test (: 632 633 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 634 require.Equal(t, appsec.BanRemediation, responses[0].Action) 635 require.Equal(t, http.StatusForbidden, statusCode) 636 require.Equal(t, appsec.BanRemediation, appsecResponse.Action) 637 require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus) 638 }, 639 }, 640 } 641 642 for _, test := range tests { 643 t.Run(test.name, func(t *testing.T) { 644 loadAppSecEngine(test, t) 645 }) 646 } 647 } 648 func TestOnMatchRemediationHooks(t *testing.T) { 649 tests := []appsecRuleTest{ 650 { 651 name: "set remediation to allow with on_match hook", 652 expected_load_ok: true, 653 inband_rules: []appsec_rule.CustomRule{ 654 { 655 Name: "rule42", 656 Zones: []string{"ARGS"}, 657 Variables: []string{"foo"}, 658 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 659 Transform: []string{"lowercase"}, 660 }, 661 }, 662 input_request: appsec.ParsedRequest{ 663 RemoteAddr: "1.2.3.4", 664 Method: "GET", 665 URI: "/urllll", 666 Args: url.Values{"foo": []string{"toto"}}, 667 }, 668 on_match: []appsec.Hook{ 669 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('allow')"}}, 670 }, 671 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 672 require.Equal(t, appsec.AllowRemediation, appsecResponse.Action) 673 require.Equal(t, http.StatusOK, appsecResponse.HTTPStatus) 674 }, 675 }, 676 { 677 name: "set remediation to captcha + custom user code with on_match hook", 678 expected_load_ok: true, 679 inband_rules: []appsec_rule.CustomRule{ 680 { 681 Name: "rule42", 682 Zones: []string{"ARGS"}, 683 Variables: []string{"foo"}, 684 Match: appsec_rule.Match{Type: "regex", Value: "^toto"}, 685 Transform: []string{"lowercase"}, 686 }, 687 }, 688 input_request: appsec.ParsedRequest{ 689 RemoteAddr: "1.2.3.4", 690 Method: "GET", 691 URI: "/urllll", 692 Args: url.Values{"foo": []string{"toto"}}, 693 }, 694 DefaultRemediation: appsec.AllowRemediation, 695 on_match: []appsec.Hook{ 696 {Filter: "IsInBand == true", Apply: []string{"SetRemediation('captcha')", "SetReturnCode(418)"}}, 697 }, 698 output_asserts: func(events []types.Event, responses []appsec.AppsecTempResponse, appsecResponse appsec.BodyResponse, statusCode int) { 699 spew.Dump(responses) 700 spew.Dump(appsecResponse) 701 702 log.Errorf("http status : %d", statusCode) 703 require.Equal(t, appsec.CaptchaRemediation, appsecResponse.Action) 704 require.Equal(t, http.StatusTeapot, appsecResponse.HTTPStatus) 705 require.Equal(t, http.StatusForbidden, statusCode) 706 }, 707 }, 708 } 709 for _, test := range tests { 710 t.Run(test.name, func(t *testing.T) { 711 loadAppSecEngine(test, t) 712 }) 713 } 714 }