Skip to content

Commit

Permalink
Add unit tests for non-error cases. (envoyproxy#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraaga committed Aug 25, 2022
1 parent c770bad commit f8f4717
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 66 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
270 changes: 207 additions & 63 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,230 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest"
"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)
})
}

0 comments on commit f8f4717

Please sign in to comment.