Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sni-misdirect plugin for go #720

Merged
merged 4 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/wasm-go/extensions/sni-misdirect/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
14 changes: 14 additions & 0 deletions plugins/wasm-go/extensions/sni-misdirect/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
20 changes: 20 additions & 0 deletions plugins/wasm-go/extensions/sni-misdirect/go.sum
Original file line number Diff line number Diff line change
@@ -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=
94 changes: 94 additions & 0 deletions plugins/wasm-go/extensions/sni-misdirect/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"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 = stripPortFromHost(host)
if string(sni) == host {
return types.ActionContinue
}
johnlanni marked this conversation as resolved.
Show resolved Hide resolved
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("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
}
100 changes: 100 additions & 0 deletions test/e2e/conformance/tests/go-wasm-sni-misdirect.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
},
}
46 changes: 46 additions & 0 deletions test/e2e/conformance/tests/go-wasm-sni-misdirect.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading