diff --git a/go.mod b/go.mod index 69220c11c871b..6fcd9ca47852c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/corazawaf/coraza-proxy-wasm go 1.19 require ( - github.com/corazawaf/coraza/v3 v3.0.0-20230203020113-719e7edfc0ac + github.com/corazawaf/coraza/v3 v3.0.0-20230203191834-6a4986af664c github.com/stretchr/testify v1.8.0 github.com/tetratelabs/proxy-wasm-go-sdk v0.21.0 github.com/tidwall/gjson v1.14.4 diff --git a/go.sum b/go.sum index e78117d20ba21..3319b1138b042 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/corazawaf/coraza/v3 v3.0.0-20230203020113-719e7edfc0ac h1:7Z7S20RGR6kZNBjjhP0bPWdgVQuF1XqNIxxl9myBt/o= -github.com/corazawaf/coraza/v3 v3.0.0-20230203020113-719e7edfc0ac/go.mod h1:dXFswKzaDVm4SsHAyvi12A4yLfg2bVx/myCBkyGALGU= +github.com/corazawaf/coraza/v3 v3.0.0-20230203191834-6a4986af664c h1:M9t+XPN3amVVuhFQcH3eOHD0ye7Wi1IquMyinqrfKFI= +github.com/corazawaf/coraza/v3 v3.0.0-20230203191834-6a4986af664c/go.mod h1:dXFswKzaDVm4SsHAyvi12A4yLfg2bVx/myCBkyGALGU= github.com/corazawaf/libinjection-go v0.1.2 h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM= github.com/corazawaf/libinjection-go v0.1.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/main_test.go b/main_test.go index 97ca92bbfa088..4940f999cac5b 100644 --- a/main_test.go +++ b/main_test.go @@ -160,6 +160,17 @@ func TestLifecycle(t *testing.T) { responded403: true, respondedNullBody: false, }, + { + name: "server name denied", + inlineRules: ` + SecRuleEngine On\nSecRule SERVER_NAME \"@streq localhost\" \"id:101,phase:1,t:lowercase,deny\" + `, + requestHdrsAction: types.ActionPause, + requestBodyAction: types.ActionContinue, + responseHdrsAction: types.ActionContinue, + responded403: true, + respondedNullBody: false, + }, { name: "request header value accepted", inlineRules: ` @@ -968,6 +979,64 @@ func TestRetrieveAddressInfo(t *testing.T) { }) } +func TestParseServerName(t *testing.T) { + testCases := map[string]struct { + autorityHeader string + expServerName string + }{ + "authority with port": { + autorityHeader: "coraza.io:443", + expServerName: "coraza.io", + }, + "authority without port": { + autorityHeader: "coraza.io", + expServerName: "coraza.io", + }, + "IPv6 with port": { + autorityHeader: "[2001:db8::1]:8080", + expServerName: "2001:db8::1", + }, + "IPv6": { + autorityHeader: "2001:db8::1", + expServerName: "2001:db8::1", + }, + "bad format": { + autorityHeader: "hostA:hostB:8080", + expServerName: "hostA:hostB:8080", + }, + } + vmTest(t, func(t *testing.T, vm types.VMContext) { + for name, tCase := range testCases { + inlineRules := fmt.Sprintf(` + SecRuleEngine On\nSecRule SERVER_NAME \"@streq %s\" \"id:101,phase:1,deny\"`, tCase.expServerName) + + conf := `{}` + if inlineRules := strings.TrimSpace(inlineRules); inlineRules != "" { + conf = fmt.Sprintf(`{"rules": ["%s"]}`, inlineRules) + } + t.Run(name, func(t *testing.T) { + opt := proxytest. + NewEmulatorOption(). + WithVMContext(vm). + WithPluginConfiguration([]byte(conf)) + + host, reset := proxytest.NewHostEmulator(opt) + defer reset() + + require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) + id := host.InitializeHttpContext() + reqHdrs := [][2]string{ + {":path", "/hello"}, + {":method", "GET"}, + {":authority", tCase.autorityHeader}, + } + action := host.CallOnRequestHeaders(id, reqHdrs, false) + require.Equal(t, types.ActionPause, action) + }) + } + }) +} + func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { t.Helper() diff --git a/wasmplugin/plugin.go b/wasmplugin/plugin.go index a72681a8ec42f..b30ad0f16f32e 100644 --- a/wasmplugin/plugin.go +++ b/wasmplugin/plugin.go @@ -159,6 +159,7 @@ func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t authority, err := proxywasm.GetHttpRequestHeader(":authority") if err == nil { tx.AddRequestHeader("Host", authority) + tx.SetServerName(parseServerName(authority)) } interruption := tx.ProcessRequestHeaders() @@ -533,3 +534,15 @@ func replaceResponseBodyWhenInterrupted(bodySize int) types.Action { proxywasm.LogWarn("response body intervention occurred: body replaced") return types.ActionContinue } + +// parseServerName parses :authority pseudo-header in order to retrieve the +// virtual host. +func parseServerName(authority string) string { + host, _, err := net.SplitHostPort(authority) + if err != nil { + // missing port or bad format + proxywasm.LogDebugf("failed to parse server name from authority %q, %v", authority, err) + host = authority + } + return host +}