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  }