From f8f47175785cd6a54e6cd37336340c8e2bcbbd7d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 25 Aug 2022 17:23:21 +0900 Subject: [PATCH] Add unit tests for non-error cases. (#5) --- go.mod | 2 +- go.sum | 4 +- main.go | 2 + main_test.go | 270 +++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 212 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 6516355446fe0..077757ad45e15 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/corazawaf/coraza/v3 v3.0.0-20220818013656-f749c07295aa github.com/stretchr/testify v1.7.1 - github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220823044833-fcfdccc01500 + github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220825081430-0fa40edeb849 github.com/tidwall/gjson v1.14.2 ) diff --git a/go.sum b/go.sum index 3cda25d59203f..335c1d5d78186 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220823044833-fcfdccc01500 h1:b8Z3Fy4l7zzv9fn4K+8RmWkGG/trGspQHpRmDJepHUk= -github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220823044833-fcfdccc01500/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4= +github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220825081430-0fa40edeb849 h1:DzsvWwG6QyWMUtWpMx4syg5bHypq8hhAUdxQAF04G68= +github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220825081430-0fa40edeb849/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4= github.com/tetratelabs/wazero v0.0.0-20220819021810-7f8e629c653f h1:+InPNMTyR4bufxW+MzqigSOXpe9Ph++NOIz/N9wtEYs= github.com/tetratelabs/wazero v0.0.0-20220819021810-7f8e629c653f/go.mod h1:CD5smBN5rGZo7UNe8aUiWyYE3bDWED/CQSonog9NSEg= github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= diff --git a/main.go b/main.go index 233f99453dfa9..b59462f12c3fd 100644 --- a/main.go +++ b/main.go @@ -59,11 +59,13 @@ func (ctx *corazaPlugin) OnPluginStart(pluginConfigurationSize int) types.OnPlug parser, err := seclang.NewParser(waf) if err != nil { proxywasm.LogCriticalf("failed to create seclang parser: %v", err) + return types.OnPluginStartStatusFailed } err = parser.FromString(config.rules) if err != nil { proxywasm.LogCriticalf("failed to parse rules: %v", err) + return types.OnPluginStartStatusFailed } ctx.waf = waf diff --git a/main_test.go b/main_test.go index 81932bef5f03a..c78ca6d413658 100644 --- a/main_test.go +++ b/main_test.go @@ -6,8 +6,10 @@ package main import ( + "fmt" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -15,77 +17,219 @@ import ( "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" ) -func TestHttpHeaders_OnHttpRequestHeaders(t *testing.T) { +func TestLifecycle(t *testing.T) { + reqHdrs := [][2]string{ + {":path", "/hello"}, + {":method", "GET"}, + {":authority", "localhost"}, + {"User-Agent", "gotest"}, + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Content-Length", "32"}, + } + reqBody := []byte(`animal=bear&food=honey&name=pooh`) + respHdrs := [][2]string{ + {":status", "200"}, + {"Server", "gotest"}, + {"Content-Length", "11"}, + {"Content-Type", "text/plain"}, + } + respBody := []byte(`Hello, yogi!`) + tests := []struct { - name string - path string - expectedAction types.Action - responded403 bool + name string + rules string + responded403 bool }{ { - name: "not matching URL", - path: "/", - expectedAction: types.ActionContinue, - responded403: false, + name: "url accepted", + rules: ` +SecRuleEngine On\nSecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "url denied", + rules: ` +SecRuleEngine On\nSecRule REQUEST_URI \"@streq /hello\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "method accepted", + rules: ` +SecRuleEngine On\nSecRule REQUEST_METHOD \"@streq post\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "method denied", + rules: ` +SecRuleEngine On\nSecRule REQUEST_METHOD \"@streq get\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "request header name accepted", + rules: ` +SecRuleEngine On\nSecRule REQUEST_HEADERS_NAMES \"@streq accept-encoding\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "request header name denied", + rules: ` +SecRuleEngine On\nSecRule REQUEST_HEADERS_NAMES \"@streq user-agent\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "request header value accepted", + rules: ` +SecRuleEngine On\nSecRule REQUEST_HEADERS:user-agent \"@streq rusttest\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "request header value denied", + rules: ` +SecRuleEngine On\nSecRule REQUEST_HEADERS:user-agent \"@streq gotest\" \"id:101,phase:1,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "request body accepted", + rules: ` +SecRuleEngine On\nSecRequestBodyAccess On\nSecRule REQUEST_BODY \"name=yogi\" \"id:101,phase:2,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "request body denied", + rules: ` +SecRuleEngine On\nSecRequestBodyAccess On\nSecRule REQUEST_BODY \"name=pooh\" \"id:101,phase:2,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "status accepted", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_STATUS \"500\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "status denied", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_STATUS \"200\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "response header name accepted", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_HEADERS_NAMES \"@streq transfer-encoding\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "response header name denied", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_HEADERS_NAMES \"@streq server\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: true, + }, + { + name: "response header value accepted", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_HEADERS:server \"@streq rusttest\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "response header value denied", + rules: ` +SecRuleEngine On\nSecRule RESPONSE_HEADERS:server \"@streq gotest\" \"id:101,phase:3,t:lowercase,deny\" +`, + responded403: true, }, { - name: "matching URL", - path: "/admin", - expectedAction: types.ActionContinue, - responded403: true, + name: "response body accepted", + rules: ` +SecRuleEngine On\nSecResponseBodyAccess On\nSecRule RESPONSE_BODY \"@contains pooh\" \"id:101,phase:4,t:lowercase,deny\" +`, + responded403: false, + }, + { + name: "response body denied", + rules: ` +SecRuleEngine On\nSecResponseBodyAccess On\nSecRule RESPONSE_BODY \"@contains yogi\" \"id:101,phase:4,t:lowercase,deny\" +`, + responded403: true, }, } - for _, runner := range []string{"go", "wasm"} { - t.Run(runner, func(t *testing.T) { - var vm types.VMContext - switch runner { - case "go": - vm = &vmContext{} - case "wasm": - wasm, err := os.ReadFile(filepath.Join("build", "main.wasm")) - if err != nil { - t.Skip("wasm not found") - } - v, err := proxytest.NewWasmVMContext(wasm) - require.NoError(t, err) - vm = v - } - - for _, tc := range tests { - tt := tc - - t.Run(tt.name, func(t *testing.T) { - opt := proxytest. - NewEmulatorOption(). - WithVMContext(vm). - WithPluginConfiguration([]byte(` + vmTest(t, func(t *testing.T, vm types.VMContext) { + for _, tc := range tests { + tt := tc + + t.Run(tt.name, func(t *testing.T) { + opt := proxytest. + NewEmulatorOption(). + WithVMContext(vm). + WithPluginConfiguration([]byte(fmt.Sprintf(` { - "rules" : "SecRuleEngine On\nSecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\"" + "rules" : "%s" } - `)) - host, reset := proxytest.NewHostEmulator(opt) - defer reset() - - require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) - - // Initialize http context. - id := host.InitializeHttpContext() - - // Call OnHttpRequestHeaders. - hs := [][2]string{{":path", tt.path}, {":method", "GET"}} - action := host.CallOnRequestHeaders(id, hs, false) - require.Equal(t, tt.expectedAction, action) - - // Call OnHttpStreamDone. - host.CompleteHttpContext(id) - - if tt.responded403 { - resp := host.GetSentLocalResponse(id) - require.EqualValues(t, 403, resp.StatusCode) - } - }) - } - }) - } + `, strings.TrimSpace(tt.rules)))) + + host, reset := proxytest.NewHostEmulator(opt) + defer reset() + + require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) + + id := host.InitializeHttpContext() + + action := host.CallOnRequestHeaders(id, reqHdrs, false) + require.Equal(t, types.ActionContinue, action) + + action = host.CallOnRequestBody(id, reqBody, true) + require.Equal(t, types.ActionContinue, action) + + action = host.CallOnResponseHeaders(id, respHdrs, false) + require.Equal(t, types.ActionContinue, action) + + action = host.CallOnResponseBody(id, respBody, true) + + // Call OnHttpStreamDone. + host.CompleteHttpContext(id) + + pluginResp := host.GetSentLocalResponse(id) + if tt.responded403 { + require.NotNil(t, pluginResp) + require.EqualValues(t, 403, pluginResp.StatusCode) + } else { + require.Nil(t, pluginResp) + } + }) + } + }) +} + +func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { + t.Helper() + + t.Run("go", func(t *testing.T) { + f(t, &vmContext{}) + }) + + t.Run("wasm", func(t *testing.T) { + wasm, err := os.ReadFile(filepath.Join("build", "main.wasm")) + if err != nil { + t.Skip("wasm not found") + } + v, err := proxytest.NewWasmVMContext(wasm) + require.NoError(t, err) + defer v.Close() + f(t, v) + }) }