From d5eee9948e74e6327b2b53ea87bb9112fc28b03d Mon Sep 17 00:00:00 2001 From: pwjagrullar Date: Fri, 14 Jun 2024 10:36:39 +0200 Subject: [PATCH] Allow empty path when HTTP method is CONNECT (#270) --- main_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ wasmplugin/plugin.go | 41 +++++++++++++++++++++--------------- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/main_test.go b/main_test.go index 441ddf6..6d34e08 100644 --- a/main_test.go +++ b/main_test.go @@ -615,6 +615,13 @@ func TestBadRequest(t *testing.T) { }, msg: "Failed to get :method", }, + { + name: "missing method and path", + reqHdrs: [][2]string{ + {":authority", "localhost"}, + }, + msg: "Failed to get :method", + }, } vmTest(t, func(t *testing.T, vm types.VMContext) { @@ -1331,6 +1338,48 @@ func TestParseServerName(t *testing.T) { }) } +func TestHttpConnectRequest(t *testing.T) { + tests := []struct { + name string + reqHdrs [][2]string + logCount int + }{ + { + name: "CONNECT", + reqHdrs: [][2]string{ + {":method", "CONNECT"}, + {":authority", "localhost"}, + }, + logCount: 0, + }, + } + + vmTest(t, func(t *testing.T, vm types.VMContext) { + for _, tc := range tests { + tt := tc + t.Run(tt.name, func(t *testing.T) { + conf := `{"directives_map": {"default": []}, "default_directives": "default"}` + 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() + + action := host.CallOnRequestHeaders(id, tt.reqHdrs, false) + require.Equal(t, types.ActionContinue, action) + + require.Equal(t, len(host.GetErrorLogs()), tt.logCount) + }) + } + }) +} + func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { t.Helper() diff --git a/wasmplugin/plugin.go b/wasmplugin/plugin.go index f2673a9..792b05d 100644 --- a/wasmplugin/plugin.go +++ b/wasmplugin/plugin.go @@ -10,6 +10,7 @@ import ( "fmt" "math" "net" + "net/http" "strconv" "strings" @@ -281,23 +282,6 @@ func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t tx.ProcessConnection(srcIP, srcPort, dstIP, dstPort) - // Note the pseudo-header :path includes the query. - // See https://httpwg.org/specs/rfc9113.html#rfc.section.8.3.1 - uri, err := proxywasm.GetHttpRequestHeader(":path") - if err != nil { - ctx.logger.Error(). - Err(err). - Msg("Failed to get :path") - propPathRaw, propPathErr := proxywasm.GetProperty([]string{"request", "path"}) - if propPathErr != nil { - ctx.logger.Error(). - Err(propPathErr). - Msg("Failed to get property of path of the request") - return types.ActionContinue - } - uri = string(propPathRaw) - } - method, err := proxywasm.GetHttpRequestHeader(":method") if err != nil { ctx.logger.Error(). @@ -313,6 +297,29 @@ func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t method = string(propMethodRaw) } + uri := "" + if method == http.MethodConnect { // CONNECT requests does not have a path, see https://httpwg.org/specs/rfc9110#CONNECT + // Populate uri with authority to build a proper request line + uri = authority + } else { + // Note the pseudo-header :path includes the query. + // See https://httpwg.org/specs/rfc9113.html#rfc.section.8.3.1 + uri, err = proxywasm.GetHttpRequestHeader(":path") + if err != nil { + ctx.logger.Error(). + Err(err). + Msg("Failed to get :path") + propPathRaw, propPathErr := proxywasm.GetProperty([]string{"request", "path"}) + if propPathErr != nil { + ctx.logger.Error(). + Err(propPathErr). + Msg("Failed to get property of path of the request") + return types.ActionContinue + } + uri = string(propPathRaw) + } + } + protocol, err := proxywasm.GetProperty([]string{"request", "protocol"}) if err != nil { // TODO(anuraaga): HTTP protocol is commonly required in WAF rules, we should probably