diff --git a/plugins/wasm-go/extensions/jwt-auth/README.md b/plugins/wasm-go/extensions/jwt-auth/README.md new file mode 100644 index 000000000..2f176fd54 --- /dev/null +++ b/plugins/wasm-go/extensions/jwt-auth/README.md @@ -0,0 +1,15 @@ +# 功能说明 +`jwt-auth`插件基于wasm-go实现了Token解析认证功能,可以判断Token是否有效,如果Token有效则继续访问后端微服务,Token无效或不存在直接拒绝并返回401 + +# 配置字段 +| 名称 | 数据类型 | 填写要求 | 描述 | +| ------------ | ------------ | ------------ | ------------ | +| token_secret_key | string | 必填 | 配置Token解析使用的SecretKey| +| token_headers | string | 必填 | 配置获取Token请求头名称| + +# 配置示例 +```yaml +token_secret_key: Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6 +token_headers: token +``` +此例`token_secret_key`中指定的是认证服务生成Token的SecretKey;`token_headers`是携带Token访问的请求头名称; \ No newline at end of file diff --git a/plugins/wasm-go/extensions/jwt-auth/VERSION b/plugins/wasm-go/extensions/jwt-auth/VERSION new file mode 100644 index 000000000..afaf360d3 --- /dev/null +++ b/plugins/wasm-go/extensions/jwt-auth/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/jwt-auth/go.mod b/plugins/wasm-go/extensions/jwt-auth/go.mod new file mode 100644 index 000000000..750c01abd --- /dev/null +++ b/plugins/wasm-go/extensions/jwt-auth/go.mod @@ -0,0 +1,18 @@ +module jwt-auth + +go 1.19 + +require ( + github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 + github.com/tidwall/gjson v1.16.0 +) + +require ( + github.com/google/uuid v1.3.0 // indirect + github.com/magefile/mage v1.14.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/wasilibs/nottinygc v0.3.0 // indirect +) diff --git a/plugins/wasm-go/extensions/jwt-auth/go.sum b/plugins/wasm-go/extensions/jwt-auth/go.sum new file mode 100644 index 000000000..9e65d2de2 --- /dev/null +++ b/plugins/wasm-go/extensions/jwt-auth/go.sum @@ -0,0 +1,22 @@ +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f h1:H+2fEuroddobcGs2Vom+osc8CE3SBHLz+JbM036Lo9w= +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230811015533-49269b43032f/go.mod h1:shD9qvrDS6xklAVjKYho8kHIVdW4A1vhNEOAL2miEEE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +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/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.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= +github.com/tidwall/gjson v1.16.0/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= +github.com/wasilibs/nottinygc v0.3.0 h1:0L1jsJ1MsyN5tdinmFbLfuEA0TnHRcqaBM9pDTJVJmU= +github.com/wasilibs/nottinygc v0.3.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/wasm-go/extensions/jwt-auth/main.go b/plugins/wasm-go/extensions/jwt-auth/main.go new file mode 100644 index 000000000..1ad6057d1 --- /dev/null +++ b/plugins/wasm-go/extensions/jwt-auth/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "encoding/json" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + jwt "github.com/dgrijalva/jwt-go" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" + "github.com/tidwall/gjson" +) + +// 自定义插件配置 + +func main() { + wrapper.SetCtx( + "jwt-auth", // 配置插件名称 + wrapper.ParseConfigBy(parseConfig), + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + ) +} + +type Config struct { + TokenSecretKey string // 解析Token SecretKey + TokenHeaders string // 定义获取Token请求头名称 +} + +type Res struct { + Code int `json:"code"` // 返回状态码 + Msg string `json:"msg"` // 返回信息 +} + +func parseConfig(json gjson.Result, config *Config, log wrapper.Log) error { + // 解析出配置,更新到config中 + config.TokenSecretKey = json.Get("token_secret_key").String() + config.TokenHeaders = json.Get("token_headers").String() + return nil +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Log) types.Action { + var res Res + if config.TokenHeaders == "" || config.TokenSecretKey == "" { + res.Code = 401 + res.Msg = "参数不足" + data, _ := json.Marshal(res) + _ = proxywasm.SendHttpResponse(401, nil, data, -1) + return types.ActionContinue + } + + token, err := proxywasm.GetHttpRequestHeader(config.TokenHeaders) + if err != nil { + res.Code = 401 + res.Msg = "认证失败" + data, _ := json.Marshal(res) + _ = proxywasm.SendHttpResponse(401, nil, data, -1) + return types.ActionContinue + } + valid := ParseTokenValid(token, config.TokenSecretKey) + if valid { + _ = proxywasm.ResumeHttpRequest() + return types.ActionPause + } else { + res.Code = 401 + res.Msg = "认证失败" + data, _ := json.Marshal(res) + _ = proxywasm.SendHttpResponse(401, nil, data, -1) + return types.ActionContinue + } +} + +func ParseTokenValid(tokenString, TokenSecretKey string) bool { + token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // 在这里提供用于验证签名的密钥 + return []byte(TokenSecretKey), nil + }) + return token.Valid +} diff --git a/test/e2e/conformance/tests/jwt-auth.go b/test/e2e/conformance/tests/jwt-auth.go new file mode 100644 index 000000000..fa98134f5 --- /dev/null +++ b/test/e2e/conformance/tests/jwt-auth.go @@ -0,0 +1,59 @@ +// 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/http" + "github.com/alibaba/higress/test/e2e/conformance/utils/suite" +) + +func init() { + HigressConformanceTests = append(HigressConformanceTests, WasmPluginsJwtAuth) +} + +var WasmPluginsJwtAuth = suite.ConformanceTest{ + ShortName: "WasmPluginsJwtAuth", + Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth wasmplugins.", + Manifests: []string{"tests/jwt-auth.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/info", + UnfollowRedirect: true, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 401, + }, + }, + }, + } + t.Run("WasmPlugins jwt-auth", 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/jwt-auth.yaml b/test/e2e/conformance/tests/jwt-auth.yaml new file mode 100644 index 000000000..16ddccb24 --- /dev/null +++ b/test/e2e/conformance/tests/jwt-auth.yaml @@ -0,0 +1,43 @@ +# 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: + name: httproute-app-root + namespace: higress-conformance-infra +spec: + ingressClassName: higress + rules: + - host: "foo.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: jwt-auth + namespace: higress-system +spec: + defaultConfig: + token_headers: token + token_secret_key: Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6 + url: file:///opt/plugins/wasm-go/extensions/jwt-auth/plugin.wasm \ No newline at end of file diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 975604776..c0e4a9bef 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -67,6 +67,7 @@ func TestHigressConformanceTests(t *testing.T) { } else { higressTests = []suite.ConformanceTest{ tests.WasmPluginsRequestBlock, + tests.WasmPluginsJwtAuth, } } } else {