From 90202bdedd313a8aa309560dd49e13815cdf091e Mon Sep 17 00:00:00 2001 From: sjcsjc123 <1401189096@qq.com> Date: Thu, 21 Dec 2023 00:37:28 +0800 Subject: [PATCH 1/2] feat: add sni-misdirect plugin for go Signed-off-by: sjcsjc123 <1401189096@qq.com> --- .../wasm-go/extensions/sni-misdirect/VERSION | 1 + .../wasm-go/extensions/sni-misdirect/go.mod | 14 +++ .../wasm-go/extensions/sni-misdirect/go.sum | 20 ++++ .../wasm-go/extensions/sni-misdirect/main.go | 72 +++++++++++++ .../tests/go-wasm-sni-misdirect.go | 100 ++++++++++++++++++ .../tests/go-wasm-sni-misdirect.yaml | 46 ++++++++ 6 files changed, 253 insertions(+) create mode 100644 plugins/wasm-go/extensions/sni-misdirect/VERSION create mode 100644 plugins/wasm-go/extensions/sni-misdirect/go.mod create mode 100644 plugins/wasm-go/extensions/sni-misdirect/go.sum create mode 100644 plugins/wasm-go/extensions/sni-misdirect/main.go create mode 100644 test/e2e/conformance/tests/go-wasm-sni-misdirect.go create mode 100644 test/e2e/conformance/tests/go-wasm-sni-misdirect.yaml diff --git a/plugins/wasm-go/extensions/sni-misdirect/VERSION b/plugins/wasm-go/extensions/sni-misdirect/VERSION new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/plugins/wasm-go/extensions/sni-misdirect/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/sni-misdirect/go.mod b/plugins/wasm-go/extensions/sni-misdirect/go.mod new file mode 100644 index 0000000000..c6ae41a333 --- /dev/null +++ b/plugins/wasm-go/extensions/sni-misdirect/go.mod @@ -0,0 +1,14 @@ +module wasm_go/higress/plugins/wasm-go/extensions/sni_misdirect + +go 1.19 + +require ( + github.com/alibaba/higress/plugins/wasm-go v1.3.1 + github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 + github.com/google/uuid v1.3.0 // indirect + github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect + github.com/magefile/mage v1.14.0 // indirect + github.com/tidwall/gjson v1.14.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) diff --git a/plugins/wasm-go/extensions/sni-misdirect/go.sum b/plugins/wasm-go/extensions/sni-misdirect/go.sum new file mode 100644 index 0000000000..4d2d73db91 --- /dev/null +++ b/plugins/wasm-go/extensions/sni-misdirect/go.sum @@ -0,0 +1,20 @@ +github.com/alibaba/higress/plugins/wasm-go v1.3.1 h1:d+t4W2NyqmqUz6DPZENflODfkLgdVlTfyso+nq0fSkg= +github.com/alibaba/higress/plugins/wasm-go v1.3.1/go.mod h1:WZ/68vwe8qWhusa6C4/gMwUqas0jvHWSOa1bp8iK8F4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA= +github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= +github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M= +github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/wasm-go/extensions/sni-misdirect/main.go b/plugins/wasm-go/extensions/sni-misdirect/main.go new file mode 100644 index 0000000000..06a3701f78 --- /dev/null +++ b/plugins/wasm-go/extensions/sni-misdirect/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "net" + "strings" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" +) + +func main() { + wrapper.SetCtx( + "sni-misdirect", + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + ) +} + +type Config struct { +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Log) types.Action { + // no need to check HTTP/1.0 and HTTP/1.1 + protocol, err := proxywasm.GetProperty([]string{"request", "protocol"}) + if err != nil { + log.Errorf("failed to get request protocol: %v", err) + return types.ActionContinue + } + if strings.HasPrefix(string(protocol), "HTTP/1") { + return types.ActionContinue + } + // no need to check http scheme + scheme := ctx.Scheme() + if scheme != "https" { + return types.ActionContinue + } + // no need to check grpc + contentType, err := proxywasm.GetHttpRequestHeader("content-type") + if err != nil { + log.Errorf("failed to get request content-type: %v", err) + return types.ActionContinue + } + if strings.HasPrefix(contentType, "application/grpc") { + return types.ActionContinue + } + // get sni + sni, err := proxywasm.GetProperty([]string{"connection", "requested_server_name"}) + if err != nil { + log.Errorf("failed to get requested_server_name: %v", err) + return types.ActionContinue + } + // get authority + host, err := proxywasm.GetHttpRequestHeader(":authority") + if err != nil { + log.Errorf("failed to get request authority: %v", err) + return types.ActionContinue + } + host, _, err = net.SplitHostPort(host) + if err != nil { + log.Errorf("failed to split host and port: %v", err) + return types.ActionContinue + } + if strings.HasPrefix(string(sni), "*.") { + proxywasm.SendHttpResponse(421, nil, []byte("421 Misdirected Request"), -1) + return types.ActionPause + } + if !strings.Contains(host, string(sni)[1:]) { + proxywasm.SendHttpResponse(421, nil, []byte("421 Misdirected Request"), -1) + return types.ActionPause + } + return types.ActionContinue +} diff --git a/test/e2e/conformance/tests/go-wasm-sni-misdirect.go b/test/e2e/conformance/tests/go-wasm-sni-misdirect.go new file mode 100644 index 0000000000..f80204b6fc --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-sni-misdirect.go @@ -0,0 +1,100 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import ( + "testing" + + "github.com/alibaba/higress/test/e2e/conformance/utils/cert" + "github.com/alibaba/higress/test/e2e/conformance/utils/http" + "github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes" + "github.com/alibaba/higress/test/e2e/conformance/utils/suite" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func init() { + Register(WasmPluginsSniMisdirect) +} + +var WasmPluginsSniMisdirect = suite.ConformanceTest{ + ShortName: "WasmPluginsSniMisdirect", + Description: "The Ingress in the higress-conformance-infra namespace test the sni-misdirect wasmplugins.", + Manifests: []string{"tests/go-wasm-sni-misdirect.yaml"}, + Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + // Prepare certificates and secrets for testcases + caCertOut, _, caCert, caKey := cert.MustGenerateCaCert(t) + svcCertOut, svcKeyOut := cert.MustGenerateCertWithCA(t, cert.ServerCertType, caCert, caKey, []string{"foo.com"}) + cliCertOut, cliKeyOut := cert.MustGenerateCertWithCA(t, cert.ClientCertType, caCert, caKey, nil) + fooSecret := kubernetes.ConstructTLSSecret("higress-conformance-infra", "foo-secret", svcCertOut.Bytes(), svcKeyOut.Bytes()) + fooSecretCACert := kubernetes.ConstructCASecret("higress-conformance-infra", "foo-secret-cacert", caCertOut.Bytes()) + suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{fooSecret, fooSecretCACert}, suite.Cleanup) + + testcases := []http.Assertion{ + { + Meta: http.AssertionMeta{ + TestCaseName: "case 1: http1.1 request", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/foo", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 2: https/1.1 request with sni and same with host", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/foo", + TLSConfig: &http.TLSConfig{ + SNI: "foo.com", + Certificates: http.Certificates{ + CACerts: [][]byte{caCertOut.Bytes()}, + ClientKeyPairs: []http.ClientKeyPair{{ + ClientCert: cliCertOut.Bytes(), + ClientKey: cliKeyOut.Bytes()}, + }, + }, + }, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + } + + t.Run("WasmPlugin sni-misdirect", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } + }) + }, +} diff --git a/test/e2e/conformance/tests/go-wasm-sni-misdirect.yaml b/test/e2e/conformance/tests/go-wasm-sni-misdirect.yaml new file mode 100644 index 0000000000..8d195a1759 --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-sni-misdirect.yaml @@ -0,0 +1,46 @@ +# Copyright (c) 2022 Alibaba Group Holding Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + higress.io/auth-tls-secret: foo-secret-cacert + name: wasmplugin-sni-misdirect + namespace: higress-conformance-infra +spec: + ingressClassName: higress + tls: + - hosts: + - "foo.com" + secretName: foo-secret + rules: + - host: "foo.com" + http: + paths: + - pathType: Exact + path: "/foo" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: sni-misdirect + namespace: higress-system +spec: + url: file:///opt/plugins/wasm-go/extensions/sni-misdirect/plugin.wasm \ No newline at end of file From 26a72daa7d2c8f3ed6c57a0c48c7d60f5267f264 Mon Sep 17 00:00:00 2001 From: sjcsjc123 <1401189096@qq.com> Date: Fri, 22 Dec 2023 16:49:17 +0800 Subject: [PATCH 2/2] feat: add sni-misdirect plugin for go Signed-off-by: sjcsjc123 <1401189096@qq.com> --- .../wasm-go/extensions/sni-misdirect/main.go | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/plugins/wasm-go/extensions/sni-misdirect/main.go b/plugins/wasm-go/extensions/sni-misdirect/main.go index 06a3701f78..fff460696d 100644 --- a/plugins/wasm-go/extensions/sni-misdirect/main.go +++ b/plugins/wasm-go/extensions/sni-misdirect/main.go @@ -1,7 +1,6 @@ package main import ( - "net" "strings" "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" @@ -55,18 +54,41 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Lo log.Errorf("failed to get request authority: %v", err) return types.ActionContinue } - host, _, err = net.SplitHostPort(host) - if err != nil { - log.Errorf("failed to split host and port: %v", err) + host = stripPortFromHost(host) + if string(sni) == host { return types.ActionContinue } - if strings.HasPrefix(string(sni), "*.") { - proxywasm.SendHttpResponse(421, nil, []byte("421 Misdirected Request"), -1) + if !strings.HasPrefix(string(sni), "*.") { + proxywasm.SendHttpResponse(421, nil, []byte("Misdirected Request"), -1) return types.ActionPause } if !strings.Contains(host, string(sni)[1:]) { - proxywasm.SendHttpResponse(421, nil, []byte("421 Misdirected Request"), -1) + proxywasm.SendHttpResponse(421, nil, []byte("Misdirected Request"), -1) return types.ActionPause } return types.ActionContinue } + +func stripPortFromHost(requestHost string) string { + // Find the last occurrence of ':' to locate the port. + portStart := strings.LastIndex(requestHost, ":") + + // Check if ':' is found. + if portStart != -1 { + // According to RFC3986, IPv6 address is always enclosed in "[]". + // section 3.2.2. + v6EndIndex := strings.LastIndex(requestHost, "]") + + // Check if ']' is found and its position is after the ':'. + if v6EndIndex == -1 || v6EndIndex < portStart { + // Check if there are characters after ':'. + if portStart+1 <= len(requestHost) { + // Return the substring without the port. + return requestHost[:portStart] + } + } + } + + // If no port is found or the conditions are not met, return the original requestHost. + return requestHost +}