feat(plugin): implement golang version of plugin jwt-auth (#743)

Signed-off-by: Ink33 <Ink33@smlk.org>
This commit is contained in:
Ink33
2024-06-06 10:22:51 +08:00
committed by GitHub
parent 6a40d83ec0
commit ed976c6d06
24 changed files with 2713 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
FROM scratch
COPY main.wasm plugin.wasm

View File

@@ -0,0 +1,5 @@
build:
go mod tidy
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer"
default: build

View File

@@ -0,0 +1 @@
0.1.0

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2023 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 config
type GlobalAuthType int
const (
GlobalAuthTrue GlobalAuthType = 10000 + iota
GlobalAuthFalse
GlobalAuthNoSet
)
func (c *JWTAuthConfig) GlobalAuthCheck() GlobalAuthType {
if c.GlobalAuth == nil {
return GlobalAuthNoSet
}
if *c.GlobalAuth {
return GlobalAuthTrue
}
return GlobalAuthFalse
}

View File

@@ -0,0 +1,125 @@
// Copyright (c) 2023 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 config
var (
// DefaultClaimToHeaderOverride 是 claim_to_override 中 override 字段的默认值
DefaultClaimToHeaderOverride = true
// DefaultClockSkewSeconds 是 ClockSkewSeconds 的默认值
DefaultClockSkewSeconds = int64(60)
// DefaultKeepToken 是 KeepToken 的默认值
DefaultKeepToken = true
// DefaultFromHeader 是 from_header 的默认值
DefaultFromHeader = []FromHeader{{
Name: "Authorization",
ValuePrefix: "Bearer ",
}}
// DefaultFromParams 是 from_params 的默认值
DefaultFromParams = []string{"access_token"}
// DefaultFromCookies 是 from_cookies 的默认值
DefaultFromCookies = []string{}
)
// JWTAuthConfig defines the struct of the global config of higress wasm plugin jwt-auth.
// https://higress.io/zh-cn/docs/plugins/jwt-auth
type JWTAuthConfig struct {
// 全局配置
//
// Consumers 配置服务的调用者,用于对请求进行认证
Consumers []*Consumer `json:"consumers"`
// 全局配置
//
// GlobalAuth 若配置为true则全局生效认证机制;
// 若配置为false则只对做了配置的域名和路由生效认证机制;
// 若不配置则仅当没有域名和路由配置时全局生效(兼容机制)
GlobalAuth *bool `json:"global_auth,omitempty"`
// 域名和路由级配置
//
// Allow 对于符合匹配条件的请求配置允许访问的consumer名称
Allow []string `json:"allow"`
}
// Consumer 配置服务的调用者,用于对请求进行认证
type Consumer struct {
// Name 配置该consumer的名称
Name string `json:"name"`
// JWKs 指定的json格式字符串是由验证JWT中签名的公钥或对称密钥组成的Json Web Key Set
//
// https://www.rfc-editor.org/rfc/rfc7517
JWKs string `json:"jwks"`
// Issuer JWT的签发者需要和payload中的iss字段保持一致
Issuer string `json:"issuer"`
// ClaimsToHeaders 抽取JWT的payload中指定字段设置到指定的请求头中转发给后端
ClaimsToHeaders *[]ClaimsToHeader `json:"claims_to_headers,omitempty"`
// FromHeaders 从指定的请求头中抽取JWT
//
// 默认值为 [{"name":"Authorization","value_prefix":"Bearer "}]
//
// 只有当from_headers,from_params,from_cookies均未配置时才会使用默认值
FromHeaders *[]FromHeader `json:"from_headers,omitempty"`
// FromParams 从指定的URL参数中抽取JWT
//
// 默认值为 access_token
//
// 只有当from_headers,from_params,from_cookies均未配置时才会使用默认值
FromParams *[]string `json:"from_params,omitempty"`
// FromCookies 从指定的cookie中抽取JWT
FromCookies *[]string `json:"from_cookies,omitempty"`
// ClockSkewSeconds 校验JWT的exp和iat字段时允许的时钟偏移量单位为秒
//
// 默认值为 60
ClockSkewSeconds *int64 `json:"clock_skew_seconds,omitempty"`
// KeepToken 转发给后端时是否保留JWT
//
// 默认值为 true
KeepToken *bool `json:"keep_token,omitempty"`
}
// ClaimsToHeader 抽取JWT的payload中指定字段设置到指定的请求头中转发给后端
type ClaimsToHeader struct {
// Claim JWT payload中的指定字段要求必须是字符串或无符号整数类型
Claim string `json:"claim"`
// Header 从payload取出字段的值设置到这个请求头中转发给后端
Header string `json:"header"`
// Override true时存在同名请求头会进行覆盖false时追加同名请求头
//
// 默认值为 true
Override *bool `json:"override,omitempty"`
}
// FromHeader 从指定的请求头中抽取JWT
type FromHeader struct {
// Name 抽取JWT的请求header
Name string `json:"name"`
// ValuePrefix 对请求header的value去除此前缀剩余部分作为JWT
ValuePrefix string `json:"value_prefix"`
}

View File

@@ -0,0 +1,138 @@
// Copyright (c) 2023 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 config
import (
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/go-jose/go-jose/v3"
"github.com/tidwall/gjson"
)
// RuleSet 插件是否至少在一个 domain 或 route 上生效
var RuleSet bool
// ParseGlobalConfig 从wrapper提供的配置中解析并转换到插件运行时需要使用的配置。
// 此处解析的是全局配置,域名和路由级配置由 ParseRuleConfig 负责。
func ParseGlobalConfig(json gjson.Result, config *JWTAuthConfig, log wrapper.Log) error {
RuleSet = false
consumers := json.Get("consumers")
if !consumers.IsArray() {
return fmt.Errorf("failed to parse configuration for consumers: consumers is not a array")
}
consumerNames := map[string]struct{}{}
for _, v := range consumers.Array() {
c, err := ParseConsumer(v, consumerNames)
if err != nil {
log.Warn(err.Error())
continue
}
config.Consumers = append(config.Consumers, c)
}
if len(config.Consumers) == 0 {
return fmt.Errorf("at least one consumer should be configured for a rule")
}
return nil
}
// ParseRuleConfig 从wrapper提供的配置中解析并转换到插件运行时需要使用的配置。
// 此处解析的是域名和路由级配置,全局配置由 ParseConfig 负责。
func ParseRuleConfig(json gjson.Result, global JWTAuthConfig, config *JWTAuthConfig, log wrapper.Log) error {
// override config via global
*config = global
allow := json.Get("allow")
if !allow.Exists() {
return fmt.Errorf("allow is required")
}
if len(allow.Array()) == 0 {
return fmt.Errorf("allow cannot be empty")
}
for _, item := range allow.Array() {
config.Allow = append(config.Allow, item.String())
}
RuleSet = true
return nil
}
func ParseConsumer(consumer gjson.Result, names map[string]struct{}) (c *Consumer, err error) {
c = &Consumer{}
// 从gjson中取得原始JSON字符串并使用标准库反序列化以降低代码复杂度。
err = json.Unmarshal([]byte(consumer.Raw), c)
if err != nil {
return nil, fmt.Errorf("failed to parse consumer: %s", err.Error())
}
// 检查consumer是否重复
if _, ok := names[c.Name]; ok {
return nil, fmt.Errorf("consumer already exists: %s", c.Name)
}
// 检查JWKs是否合法
jwks := &jose.JSONWebKeySet{}
err = json.Unmarshal([]byte(c.JWKs), jwks)
if err != nil {
return nil, fmt.Errorf("jwks is invalid, consumer:%s, status:%s, jwks:%s", c.Name, err.Error(), c.JWKs)
}
// 检查是否需要使用默认jwt抽取来源
if c.FromHeaders == nil && c.FromParams == nil && c.FromCookies == nil {
c.FromHeaders = &DefaultFromHeader
c.FromParams = &DefaultFromParams
c.FromCookies = &DefaultFromCookies
}
// 检查ClaimsToHeaders
if c.ClaimsToHeaders != nil {
// header去重
c2h := map[string]struct{}{}
// 此处需要先把指针解引用到临时变量
tmp := *c.ClaimsToHeaders
for i := range tmp {
if _, ok := c2h[tmp[i].Header]; ok {
return nil, fmt.Errorf("claim to header already exists: %s", c2h[tmp[i].Header])
}
c2h[tmp[i].Header] = struct{}{}
// 为Override填充默认值
if tmp[i].Override == nil {
tmp[i].Override = &DefaultClaimToHeaderOverride
}
}
}
// 为ClockSkewSeconds填充默认值
if c.ClockSkewSeconds == nil {
c.ClockSkewSeconds = &DefaultClockSkewSeconds
}
// 为KeepToken填充默认值
if c.KeepToken == nil {
c.KeepToken = &DefaultKeepToken
}
// consumer合法记录consumer名称
names[c.Name] = struct{}{}
return c, nil
}

View File

@@ -0,0 +1,22 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth
go 1.19
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v1.3.5
github.com/go-jose/go-jose/v3 v3.0.3
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
github.com/tidwall/gjson v1.17.1
)
require (
github.com/google/uuid v1.6.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/resp v0.1.1 // indirect
golang.org/x/crypto v0.23.0 // indirect
)

View File

@@ -0,0 +1,71 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.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/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,4 @@
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,33 @@
// Copyright (c) 2023 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 handler
import (
"fmt"
cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
)
func claimsToHeader(claims map[string]any, cth []cfg.ClaimsToHeader) {
for i := range cth {
if v, ok := claims[cth[i].Claim]; ok {
if *cth[i].Override {
proxywasm.ReplaceHttpRequestHeader(cth[i].Header, fmt.Sprint(v))
}
proxywasm.AddHttpRequestHeader(cth[i].Header, fmt.Sprint(v))
}
}
}

View File

@@ -0,0 +1,146 @@
// Copyright (c) 2023 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 handler
import (
"net/url"
"strings"
cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
)
// extracToken 从三个来源中依次尝试抽取Token若找不到Token则返回空字符串
func extractToken(keepToken bool, consumer *cfg.Consumer, header HeaderProvider, log Logger) string {
token := ""
// 1. 从header中抽取token
if h := consumer.FromHeaders; h != nil {
token = extractFromHeader(keepToken, *h, header, log)
}
if token != "" {
return token
}
// 2. 从params中抽取token
if p := consumer.FromParams; p != nil {
token = extractFromParams(keepToken, *p, header, log)
}
if token != "" {
return token
}
// 3. 从cookies中抽取token
if c := consumer.FromCookies; c != nil {
token = extractFromCookies(keepToken, *c, header, log)
}
// 此处无需判空
return token
}
func extractFromHeader(keepToken bool, headers []cfg.FromHeader, header HeaderProvider, log Logger) (token string) {
for i := range headers {
// proxywasm 获取到的 header name 均为小写,此处需做修改
lowerName := strings.ToLower(headers[i].Name)
token, err := header.GetHttpRequestHeader(lowerName)
if err != nil {
log.Warnf("failed to get authorization: %v", err)
continue
}
if token != "" {
if !strings.HasPrefix(token, headers[i].ValuePrefix) {
log.Warnf("authorization has no prefix %q", headers[i].ValuePrefix)
return ""
}
if !keepToken {
_ = header.RemoveHttpRequestHeader(lowerName)
}
return strings.TrimPrefix(token, headers[i].ValuePrefix)
}
}
return ""
}
func extractFromParams(keepToken bool, params []string, header HeaderProvider, log Logger) (token string) {
urlparams, err := header.GetHttpRequestHeader(":path")
if err != nil {
log.Warnf("failed to get authorization: %v", err)
return ""
}
url, _ := url.Parse(urlparams)
query := url.Query()
for i := range params {
token := query.Get(params[i])
if token != "" {
if !keepToken {
query.Del(params[i])
}
return token
}
}
return ""
}
func extractFromCookies(keepToken bool, cookies []string, header HeaderProvider, log Logger) (token string) {
requestCookies, err := header.GetHttpRequestHeader("cookie")
if err != nil {
log.Warnf("failed to get authorization: %v", err)
return ""
}
for i := range cookies {
token := findCookie(requestCookies, cookies[i])
if token != "" {
if !keepToken {
_ = header.ReplaceHttpRequestHeader("cookie", deleteCookie(requestCookies, cookies[i]))
}
return token
}
}
return ""
}
func findCookie(cookie string, key string) string {
value := ""
pairs := strings.Split(cookie, ";")
for _, pair := range pairs {
pair = strings.TrimSpace(pair)
kv := strings.Split(pair, "=")
if kv[0] == key {
value = kv[1]
break
}
}
return value
}
func deleteCookie(cookie string, key string) string {
result := ""
pairs := strings.Split(cookie, ";")
for _, pair := range pairs {
pair = strings.TrimSpace(pair)
if !strings.HasPrefix(pair, key) {
result += pair + ";"
}
}
return strings.TrimSuffix(result, ";")
}

View File

@@ -0,0 +1,159 @@
// Copyright (c) 2023 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 handler
import (
"time"
cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
)
// jwt-auth 插件认证逻辑与 basic-auth 一致:
// - global_auth == true 开启全局生效:
// - 若当前 domain/route 未配置 allow 列表,即未配置该插件:则在所有 consumers 中查找,如果找到则认证通过,否则认证失败 (1*)
// - 若当前 domain/route 配置了该插件:则在 allow 列表中查找,如果找到则认证通过,否则认证失败
//
// - global_auth == false 非全局生效:(2*)
// - 若当前 domain/route 未配置该插件:则直接放行
// - 若当前 domain/route 配置了该插件:则在 allow 列表中查找,如果找到则认证通过,否则认证失败
//
// - global_auth 未设置:
// - 若没有一个 domain/route 配置该插件:则遵循 (1*)
// - 若有至少一个 domain/route 配置该插件:则遵循 (2*)
//
// https://github.com/alibaba/higress/blob/e09edff827b94fa5bcc149bbeadc905361100c2a/plugins/wasm-go/extensions/basic-auth/main.go#L191
func OnHTTPRequestHeaders(ctx wrapper.HttpContext, config cfg.JWTAuthConfig, log wrapper.Log) types.Action {
var (
noAllow = len(config.Allow) == 0 // 未配置 allow 列表,表示插件在该 domain/route 未生效
globalAuthNoSet = config.GlobalAuthCheck() == cfg.GlobalAuthNoSet
globalAuthSetTrue = config.GlobalAuthCheck() == cfg.GlobalAuthTrue
globalAuthSetFalse = config.GlobalAuthCheck() == cfg.GlobalAuthFalse
)
// 不需要认证而直接放行的情况:
// - global_auth == false 且 当前 domain/route 未配置该插件
// - global_auth 未设置 且 有至少一个 domain/route 配置该插件 且 当前 domain/route 未配置该插件
if globalAuthSetFalse || (cfg.RuleSet && globalAuthNoSet) {
if noAllow {
log.Info("authorization is not required")
return types.ActionContinue
}
}
header := &proxywasmProvider{}
actionMap := map[string]func() types.Action{}
unAuthzConsumer := ""
// 匹配consumer
for i := range config.Consumers {
err := consumerVerify(config.Consumers[i], time.Now(), header, log)
if err != nil {
log.Warn(err.Error())
if v, ok := err.(*ErrDenied); ok {
actionMap[config.Consumers[i].Name] = v.denied
}
continue
}
// 全局生效:
// - global_auth == true 且 当前 domain/route 未配置该插件
// - global_auth 未设置 且 没有任何一个 domain/route 配置该插件
if (globalAuthSetTrue && noAllow) || (globalAuthNoSet && !cfg.RuleSet) {
log.Infof("consumer %q authenticated", config.Consumers[i].Name)
return authenticated(config.Consumers[i].Name)
}
// 全局生效,但当前 domain/route 配置了 allow 列表
if globalAuthSetTrue && !noAllow {
if !contains(config.Consumers[i].Name, config.Allow) {
log.Warnf("jwt verify failed, consumer %q not allow",
config.Consumers[i].Name)
actionMap[config.Consumers[i].Name] = deniedUnauthorizedConsumer
unAuthzConsumer = config.Consumers[i].Name
continue
}
log.Infof("consumer %q authenticated", config.Consumers[i].Name)
return authenticated(config.Consumers[i].Name)
}
// 非全局生效
if globalAuthSetFalse || (globalAuthNoSet && cfg.RuleSet) {
if !noAllow { // 配置了 allow 列表
if !contains(config.Consumers[i].Name, config.Allow) {
log.Warnf("jwt verify failed, consumer %q not allow",
config.Consumers[i].Name)
actionMap[config.Consumers[i].Name] = deniedUnauthorizedConsumer
unAuthzConsumer = config.Consumers[i].Name
continue
}
log.Infof("consumer %q authenticated", config.Consumers[i].Name)
return authenticated(config.Consumers[i].Name)
}
}
// switch config.GlobalAuthCheck() {
// case cfg.GlobalAuthNoSet:
// if !cfg.RuleSet {
// log.Infof("consumer %q authenticated", config.Consumers[i].Name)
// return authenticated(config.Consumers[i].Name)
// }
// case cfg.GlobalAuthTrue:
// if len(config.Allow) == 0 {
// log.Infof("consumer %q authenticated", config.Consumers[i].Name)
// return authenticated(config.Consumers[i].Name)
// }
// fallthrough // 若 allow 列表不为空,则 fallthrough 到需要检查 allow 列表的逻辑中
// // 全局生效设置为 false
// case cfg.GlobalAuthFalse:
// if !contains(config.Consumers[i].Name, config.Allow) {
// log.Warnf("jwt verify failed, consumer %q not allow",
// config.Consumers[i].Name)
// actionMap[config.Consumers[i].Name] = deniedUnauthorizedConsumer
// unAuthzConsumer = config.Consumers[i].Name
// continue
// }
// log.Infof("consumer %q authenticated", config.Consumers[i].Name)
// return authenticated(config.Consumers[i].Name)
// }
}
if len(config.Allow) == 1 {
if unAuthzConsumer != "" {
log.Warnf("consumer %q denied", unAuthzConsumer)
return deniedUnauthorizedConsumer()
}
if v, ok := actionMap[config.Allow[0]]; ok {
log.Warnf("consumer %q denied", config.Allow[0])
return v()
}
}
// 拒绝兜底
log.Warnf("all consumers verify failed")
return deniedNotAllow()
}
func contains(str string, arr []string) bool {
for _, i := range arr {
if i == str {
return true
}
}
return false
}

View File

@@ -0,0 +1,212 @@
// Copyright (c) 2023 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 handler
import (
"encoding/json"
"fmt"
"time"
cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
)
var protectionSpace = "MSE Gateway" // 认证失败时,返回响应头 WWW-Authenticate: JWT realm=MSE Gateway
type ErrDenied struct {
msg string
denied func() types.Action
}
type Logger interface {
Warnf(format string, args ...interface{})
}
type HeaderProvider interface {
GetHttpRequestHeader(key string) (string, error)
ReplaceHttpRequestHeader(key string, value string) error
RemoveHttpRequestHeader(key string) error
}
type proxywasmProvider struct{}
func (p *proxywasmProvider) GetHttpRequestHeader(key string) (string, error) {
return proxywasm.GetHttpRequestHeader(key)
}
func (p *proxywasmProvider) ReplaceHttpRequestHeader(key string, value string) error {
return proxywasm.ReplaceHttpRequestHeader(key, value)
}
func (p *proxywasmProvider) RemoveHttpRequestHeader(key string) error {
return proxywasm.RemoveHttpRequestHeader(key)
}
func (e *ErrDenied) Error() string {
return e.msg
}
func consumerVerify(consumer *cfg.Consumer, verifyTime time.Time, header HeaderProvider, log Logger) error {
tokenStr := extractToken(*consumer.KeepToken, consumer, header, log)
if tokenStr == "" {
return &ErrDenied{
msg: fmt.Sprintf("jwt is missing, consumer: %s", consumer.Name),
denied: deniedJWTMissing,
}
}
// 当前版本的higress暂不支持jwe此处用ParseSigned
token, err := jwt.ParseSigned(tokenStr)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt parse failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
// 此处可以直接使用 JSON 反序列 jwks
jwks := jose.JSONWebKeySet{}
err = json.Unmarshal([]byte(consumer.JWKs), &jwks)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt parse failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
out := jwt.Claims{}
rawClaims := map[string]any{}
// 提前确认 kid 状态
var kid string
var key jose.JSONWebKey
for _, header := range token.Headers {
if header.KeyID != "" {
kid = header.KeyID
break
}
}
// 没有 kid 时选择第一个 key
if kid == "" {
key = jwks.Keys[0]
}
keys := jwks.Key(kid)
if len(keys) == 0 { // kid 不存在时选择第一个 key
key = jwks.Keys[0]
} else {
key = keys[0]
}
// Claims 支持直接传入 jose 的 jwk
// 无需额外调用verifyclaims内部已进行验证
err = token.Claims(key, &out)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
if out.Issuer != consumer.Issuer {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: issuer does not equal",
consumer.Name,
tokenStr,
),
denied: deniedJWTVerificationFails,
}
}
// 检查是否过期
err = out.ValidateWithLeeway(
jwt.Expected{
Issuer: consumer.Issuer,
Time: verifyTime,
},
time.Duration(*consumer.ClockSkewSeconds)*time.Second,
)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTExpired,
}
}
if consumer.ClaimsToHeaders != nil {
claimsToHeader(rawClaims, *consumer.ClaimsToHeaders)
}
return nil
}
func deniedJWTMissing() types.Action {
_ = proxywasm.SendHttpResponse(401, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT is missing."), -1)
return types.ActionContinue
}
func deniedJWTExpired() types.Action {
_ = proxywasm.SendHttpResponse(401, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT is expried."), -1)
return types.ActionContinue
}
func deniedJWTVerificationFails() types.Action {
_ = proxywasm.SendHttpResponse(401, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT verification fails"), -1)
return types.ActionContinue
}
func deniedUnauthorizedConsumer() types.Action {
_ = proxywasm.SendHttpResponse(403, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. Unauthorized consumer."), -1)
return types.ActionContinue
}
func deniedNotAllow() types.Action {
_ = proxywasm.SendHttpResponse(403, WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT token not allow."), -1)
return types.ActionContinue
}
func authenticated(name string) types.Action {
_ = proxywasm.AddHttpRequestHeader("X-Mse-Consumer", name)
return types.ActionContinue
}
func WWWAuthenticateHeader(realm string) [][2]string {
return [][2]string{
{"WWW-Authenticate", fmt.Sprintf("JWT realm=%s", realm)},
}
}

View File

@@ -0,0 +1,129 @@
package handler
import (
"errors"
"testing"
"time"
"github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/tidwall/gjson"
)
type testLogger struct {
T *testing.T
}
func (l *testLogger) Warnf(format string, args ...interface{}) {
l.T.Logf(format, args...)
}
type testProvider struct {
headerMap map[string]string
}
func (p *testProvider) GetHttpRequestHeader(key string) (string, error) {
if v, ok := p.headerMap[key]; ok {
return v, nil
}
return "", errors.New("no found")
}
func (p *testProvider) ReplaceHttpRequestHeader(key string, value string) error {
p.headerMap[key] = value
return nil
}
func (p *testProvider) RemoveHttpRequestHeader(key string) error {
delete(p.headerMap, key)
return nil
}
const (
ES256Allow string = "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.hm71YWfjALshUAgyOu-r9W2WBG_zfqIZZacAbc7oIH1r7dbB0sGQn3wKMWMmOzmxX0UyaVZ0KMk-HFTA1hDnBQ"
ES256Expried string = "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.9AnXd2rZ6FirHZQAoabyL4xZNz0jr-3LmcV4-pFV3JrdtUT4386Mw5Qan125fUB-rZf_ZBlv0Bft2tWY149fyg"
RS256Allow string = "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.iO0wPY91b_VNGUMZ1n-Ub-SRmEkDQMFLSi77z49tEzll3UZXwmBraP5udM_OPUAdk9ZO3dbb_fOgdcN9V1H9p5kiTr-l-pZTFTJHrPJj8wC519sYRcCk3wrZ9aXR5tNMwOsMdQb7waTBatDQLmHPWzAoTNBc8mwXkRcv1dmJLvsJgxyCl1I9CMOMPq0fYj1NBvaUDIdVSL1o7GGiriD8-0UIOmS72-I3mbaoCIyVb0h3wx7gnIW3zr0yYWaYoiIgmHLag-eEGxHp4-BjtCqcokU4QVMS91qpH7Mkl1iv2WHEkuDQRJ-nLzYGwXb7Dncx9K5tNWHJuZ-DihIU2oT0aA"
RS256Expried string = "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.jqzlhBPk9mmvtTT5aCYf-_5uXXSEU5bQ32fx78XeboCnjR9K1CsI4KYUIkXEX3bk66XJQUeSes7lz3gA4Yzkd-v9oADHTgpKnIxzv_5mD0_afIwEFjcalqVbSvCmro4PessQZDnmU7AIzoo3RPSqbmq8xbPVYUH9I-OO8aUu2ATd1HozgxJH1XnRU8k9KMkVW8XhvJXLKZJmnqe3Tu6pCU_tawFlBfBC4fAhMf0yX2CGE0ABAHubcdiI6JXObQmQQ9Or2a-g2a8g_Bw697PoPOsAn0YpTrHst9GcyTpkbNTAq9X8fc5EM7hiDM1FGeMYcaQTdMnOh4HBhP0p4YEhvA"
JWKs string = "{\"keys\":[{\"kty\":\"EC\",\"kid\":\"p256\",\"crv\":\"P-256\",\"x\":\"GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU\",\"y\":\"5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ\"},{\"kty\":\"RSA\",\"kid\":\"rsa\",\"n\":\"pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q\",\"e\":\"AQAB\"}]}"
)
const (
consumers = `{
"consumers": [
{
"name": "consumer1",
"issuer": "higress-test",
"jwks": "{\n\"keys\": [\n{\n\"kty\": \"EC\",\n\"kid\": \"p256\",\n\"crv\": \"P-256\",\n\"x\": \"GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU\",\n\"y\": \"5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ\"\n},\n{\n\"kty\": \"RSA\",\n\"kid\": \"rsa\",\n\"n\": \"pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q\",\n\"e\": \"AQAB\"\n}\n]\n}"
},
{
"name": "consumer_hedaer",
"issuer": "higress-test",
"jwks": "{\n\"keys\": [\n{\n\"kty\": \"EC\",\n\"kid\": \"p256\",\n\"crv\": \"P-256\",\n\"x\": \"GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU\",\n\"y\": \"5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ\"\n},\n{\n\"kty\": \"RSA\",\n\"kid\": \"rsa\",\n\"n\": \"pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q\",\n\"e\": \"AQAB\"\n}\n]\n}",
"from_headers": [
{
"name": "jwt",
"value_prefix": "Bearer "
}
]
},
{
"name": "consumer_params",
"issuer": "higress-test",
"jwks": "{\n\"keys\": [\n{\n\"kty\": \"EC\",\n\"kid\": \"p256\",\n\"crv\": \"P-256\",\n\"x\": \"GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU\",\n\"y\": \"5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ\"\n},\n{\n\"kty\": \"RSA\",\n\"kid\": \"rsa\",\n\"n\": \"pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q\",\n\"e\": \"AQAB\"\n}\n]\n}",
"from_params": [
"jwt_token"
]
},
{
"name": "consumer_cookies",
"issuer": "higress-test",
"jwks": "{\n\"keys\": [\n{\n\"kty\": \"EC\",\n\"kid\": \"p256\",\n\"crv\": \"P-256\",\n\"x\": \"GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU\",\n\"y\": \"5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ\"\n},\n{\n\"kty\": \"RSA\",\n\"kid\": \"rsa\",\n\"n\": \"pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q\",\n\"e\": \"AQAB\"\n}\n]\n}",
"from_cookies": [
"jwt_token"
]
}
]
}`
)
func TestConsumerVerify(t *testing.T) {
log := &testLogger{
T: t,
}
cs := []*config.Consumer{}
c := gjson.Parse(consumers).Get("consumers")
if !c.IsArray() {
t.Error("failed to parse configuration for consumers: consumers is not a array")
return
}
consumerNames := map[string]struct{}{}
for _, v := range c.Array() {
c, err := config.ParseConsumer(v, consumerNames)
if err != nil {
t.Log(err.Error())
continue
}
cs = append(cs, c)
}
if len(cs) == 0 {
t.Error("at least one consumer should be configured for a rule")
return
}
header := &testProvider{headerMap: map[string]string{"jwt": "Bearer " + ES256Allow}}
err := consumerVerify(&config.Consumer{
Name: "consumer1",
JWKs: JWKs,
Issuer: "higress-test",
ClaimsToHeaders: &[]config.ClaimsToHeader{},
FromHeaders: &[]config.FromHeader{{Name: "jwt", ValuePrefix: "Bearer "}},
ClockSkewSeconds: &config.DefaultClockSkewSeconds,
KeepToken: &config.DefaultKeepToken,
}, time.Now(), header, log)
if err != nil {
if v, ok := err.(*ErrDenied); ok {
t.Error(v.msg)
}
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2023 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 main
import (
"github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/handler"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
// @Name jwt-proxy
// @Category auth
// @Phase UNSPECIFIED_PHASE
// @Priority 0
// @Title zh-CN jwt验证
// @Description zh-CN 通过jwt进行验证
// @Version 0.1.0
//
// @Contact.name Ink33
// @Contact.url https://github.com/Ink-33
// @Contact.email ink33@smlk.org
//
// @Example
//{}
// @End
func main() {
wrapper.SetCtx(
// 插件名称
"jwt-auth",
// 为解析插件配置,设置自定义函数
wrapper.ParseConfigBy(config.ParseGlobalConfig),
wrapper.ParseOverrideConfigBy(config.ParseGlobalConfig, config.ParseRuleConfig),
// 为处理请求头,设置自定义函数
wrapper.ProcessRequestHeadersBy(handler.OnHTTPRequestHeaders),
)
}

View File

@@ -0,0 +1,52 @@
# File generated by hgctl. Modify as required.
version: 1.0.0
build:
# The official builder image version
builder:
go: 1.19
tinygo: 0.28.1
oras: 1.0.0
# The WASM plugin project directory
input: ./
# The output of the build products
output:
# Choose between 'files' and 'image'
type: files
# Destination address: when type=files, specify the local directory path, e.g., './out' or
# type=image, specify the remote docker repository, e.g., 'docker.io/<your_username>/<your_image>'
dest: ./out
# The authentication configuration for pushing image to the docker repository
docker-auth: ~/.docker/config.json
# The directory for the WASM plugin configuration structure
model-dir: ./
# The WASM plugin configuration structure name
model: PluginConfig
# Enable debug mode
debug: false
test:
# Test environment name, that is a docker compose project name
name: wasm-test
# The output path to build products, that is the source of test configuration parameters
from-path: ./out
# The test configuration source
test-path: ./test
# Docker compose configuration, which is empty, looks for the following files from 'test-path':
# compose.yaml, compose.yml, docker-compose.yml, docker-compose.yaml
compose-file:
# Detached mode: Run containers in the background
detach: false
install:
# The namespace of the installation
namespace: higress-system
# Use to validate WASM plugin configuration when install by yaml
spec-yaml: ./out/spec.yaml
# Installation source. Choose between 'from-yaml' and 'from-go-project'
from-yaml: ./test/plugin-conf.yaml
# If 'from-go-src' is non-empty, the output type of the build option must be 'image'
from-go-src:
# Enable debug mode
debug: false

View File

@@ -0,0 +1,144 @@
package test
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"os"
"testing"
"time"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
)
type keySet struct {
Name string
PrivateKey any
PublicKey any
}
type jwts struct {
JWTs []struct {
Algorithm string `json:"alg"`
Token string `json:"token"`
Type string `json:"type"`
} `json:"jwts"`
}
func genPrivateKey() (keySets map[string]keySet) {
keySets = map[string]keySet{}
rsaPri, _ := rsa.GenerateKey(rand.Reader, 2048)
keySets["rsa"] = keySet{Name: "rsa", PrivateKey: rsaPri, PublicKey: &rsaPri.PublicKey}
// ed25519pri, ed25519pub, _ := ed25519.GenerateKey(rand.Reader)
// keySets["ed25519"] = keySet{Name: "ed25519", PrivateKey: ed25519pri, PublicKey: ed25519pub}
p256Pri, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
keySets["p256"] = keySet{Name: "p256", PrivateKey: p256Pri, PublicKey: &p256Pri.PublicKey}
// p384Pri, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
// keySets = append(keySets, keySet{Name: "p384", PrivateKey: p384Pri, PublicKey: &p384Pri.PublicKey})
// p521Pri, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
// keySets = append(keySets, keySet{Name: "p521", PrivateKey: p521Pri, PublicKey: &p521Pri.PublicKey})
return
}
func genJWKs(keySets map[string]keySet) (keys jose.JSONWebKeySet) {
for k := range keySets {
k := jose.JSONWebKey{
Key: keySets[k].PublicKey,
KeyID: keySets[k].Name,
}
keys.Keys = append(keys.Keys, k)
}
return
}
func genJWTs(keySets map[string]keySet) (jwts jwts) {
claims := map[string]jwt.Claims{
"normal": {
Issuer: "higress-test",
Subject: "higress-test",
Audience: []string{"foo", "bar"},
Expiry: jwt.NewNumericDate(time.Date(2034, 1, 1, 0, 0, 0, 0, time.UTC)),
NotBefore: jwt.NewNumericDate(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
},
"expried": {
Issuer: "higress-test",
Subject: "higress-test",
Audience: []string{"foo", "bar"},
Expiry: jwt.NewNumericDate(time.Date(2024, 1, 1, 0, 0, 0, 1, time.UTC)),
NotBefore: jwt.NewNumericDate(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
},
}
sigrsa, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.RS256,
Key: keySets["rsa"].PrivateKey,
}, (&jose.SignerOptions{}).WithType("JWT").WithHeader(jose.HeaderKey("kid"), "rsa"))
if err != nil {
panic(err)
}
sigp256, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.ES256,
Key: keySets["p256"].PrivateKey,
}, (&jose.SignerOptions{}).WithType("JWT").WithHeader(jose.HeaderKey("kid"), "p256"))
if err != nil {
panic(err)
}
sigs := map[string]jose.Signer{
"RS256": sigrsa,
"ES256": sigp256,
}
for k1, v1 := range sigs {
for k2, v2 := range claims {
raw, _ := jwt.Signed(v1).Claims(v2).CompactSerialize()
jwts.JWTs = append(jwts.JWTs, struct {
Algorithm string "json:\"alg\""
Token string "json:\"token\""
Type string "json:\"type\""
}{
Algorithm: k1,
Token: raw,
Type: k2,
})
}
}
return
}
func TestMain(m *testing.M) {
keySets := genPrivateKey()
keys := genJWKs(keySets)
jwts := genJWTs(keySets)
jwks, err := json.Marshal(keys)
if err != nil {
panic(err)
}
f, _ := os.Create("keys.json")
if err != nil {
panic(err)
}
defer f.Close()
f.WriteString(string(jwks))
jwtsm, err := json.Marshal(&jwts)
if err != nil {
panic(err)
}
f, _ = os.Create("jwts.json")
if err != nil {
panic(err)
}
defer f.Close()
f.WriteString(string(jwtsm))
m.Run()
}

View File

@@ -0,0 +1,24 @@
{
"jwts": [
{
"alg": "RS256",
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.iO0wPY91b_VNGUMZ1n-Ub-SRmEkDQMFLSi77z49tEzll3UZXwmBraP5udM_OPUAdk9ZO3dbb_fOgdcN9V1H9p5kiTr-l-pZTFTJHrPJj8wC519sYRcCk3wrZ9aXR5tNMwOsMdQb7waTBatDQLmHPWzAoTNBc8mwXkRcv1dmJLvsJgxyCl1I9CMOMPq0fYj1NBvaUDIdVSL1o7GGiriD8-0UIOmS72-I3mbaoCIyVb0h3wx7gnIW3zr0yYWaYoiIgmHLag-eEGxHp4-BjtCqcokU4QVMS91qpH7Mkl1iv2WHEkuDQRJ-nLzYGwXb7Dncx9K5tNWHJuZ-DihIU2oT0aA",
"type": "normal"
},
{
"alg": "RS256",
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.jqzlhBPk9mmvtTT5aCYf-_5uXXSEU5bQ32fx78XeboCnjR9K1CsI4KYUIkXEX3bk66XJQUeSes7lz3gA4Yzkd-v9oADHTgpKnIxzv_5mD0_afIwEFjcalqVbSvCmro4PessQZDnmU7AIzoo3RPSqbmq8xbPVYUH9I-OO8aUu2ATd1HozgxJH1XnRU8k9KMkVW8XhvJXLKZJmnqe3Tu6pCU_tawFlBfBC4fAhMf0yX2CGE0ABAHubcdiI6JXObQmQQ9Or2a-g2a8g_Bw697PoPOsAn0YpTrHst9GcyTpkbNTAq9X8fc5EM7hiDM1FGeMYcaQTdMnOh4HBhP0p4YEhvA",
"type": "expried"
},
{
"alg": "ES256",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.9AnXd2rZ6FirHZQAoabyL4xZNz0jr-3LmcV4-pFV3JrdtUT4386Mw5Qan125fUB-rZf_ZBlv0Bft2tWY149fyg",
"type": "expried"
},
{
"alg": "ES256",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.hm71YWfjALshUAgyOu-r9W2WBG_zfqIZZacAbc7oIH1r7dbB0sGQn3wKMWMmOzmxX0UyaVZ0KMk-HFTA1hDnBQ",
"type": "normal"
}
]
}

View File

@@ -0,0 +1,17 @@
{
"keys": [
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
},
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}

View File

@@ -0,0 +1,142 @@
# Copyright (c) 2024 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: wasmplugin-jwt-auth
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:
consumers:
- name: consumer1
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
},
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
- name: consumer_hedaer
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
},
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
from_headers:
- name: jwt
value_prefix: "Bearer "
- name: consumer_params
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
},
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
from_params:
- jwt_token
- name: consumer_cookies
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
},
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
from_cookies:
- jwt_token
global_auth: false
defaultConfigDisable: false
matchRules:
- config:
allow:
- consumer1
- consumer_hedaer
- consumer_params
- consumer_cookies
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-jwt-auth
url: file:///opt/plugins/wasm-go/extensions/jwt-auth/plugin.wasm

View File

@@ -0,0 +1,131 @@
# Copyright (c) 2024 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: wasmplugin-jwt-auth
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:
consumers:
- name: consumerEC
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
- name: consumerRSA
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
- name: consumerEC_hedaer
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_headers:
- name: jwt
value_prefix: "Bearer "
- name: consumerEC_params
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_params:
- jwt_token
- name: consumerEC_cookies
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_cookies:
- jwt_token
global_auth: false
defaultConfigDisable: false
matchRules:
- config:
allow:
- consumerEC
- consumerEC_hedaer
- consumerEC_params
- consumerEC_cookies
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-jwt-auth
url: file:///opt/plugins/wasm-go/extensions/jwt-auth/plugin.wasm

View File

@@ -0,0 +1,128 @@
# Copyright (c) 2024 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: wasmplugin-jwt-auth
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:
consumers:
- name: consumerEC
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
- name: consumerRSA
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "RSA",
"kid": "rsa",
"n": "pFKAKJ0V3vFwGTvBSHbPwrNdvPyr-zMTh7Y9IELFIMNUQfG9_d2D1wZcrX5CPvtEISHin3GdPyfqEX6NjPyqvCLFTuNh80-r5Mvld-A5CHwITZXz5krBdqY5Z0wu64smMbzst3HNxHbzLQvHUY-KS6hceOB84d9B4rhkIJEEAWxxIA7yPJYjYyIC_STpPddtJkkweVvoa0m0-_FQkDFsbRS0yGgMNG4-uc7qLIU4kSwMQWcw1Rwy39LUDP4zNzuZABbWsDDBsMlVUaszRdKIlk5AQ-Fkah3E247dYGUQjSQ0N3dFLlMDv_e62BT3IBXGLg7wvGosWFNT_LpIenIW6Q",
"e": "AQAB"
}
]
}
- name: consumerEC_hedaer
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_headers:
- name: jwt
value_prefix: "Bearer "
- name: consumerEC_params
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_params:
- jwt_token
- name: consumerEC_cookies
issuer: higress-test
jwks: |-
{
"keys": [
{
"kty": "EC",
"kid": "p256",
"crv": "P-256",
"x": "GWym652nfByDbs4EzNpGXCkdjG03qFZHulNDHTo3YJU",
"y": "5uVg_n-flqRJ5Zhf_aEKS0ow9SddTDgxGduSCgpoAZQ"
}
]
}
from_cookies:
- jwt_token
global_auth: false
defaultConfigDisable: false
matchRules:
- config:
allow:
- consumerEC
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-jwt-auth
url: file:///opt/plugins/wasm-go/extensions/jwt-auth/plugin.wasm

View File

@@ -0,0 +1,944 @@
// Copyright (c) 2024 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"
)
const (
ES256Allow string = "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.hm71YWfjALshUAgyOu-r9W2WBG_zfqIZZacAbc7oIH1r7dbB0sGQn3wKMWMmOzmxX0UyaVZ0KMk-HFTA1hDnBQ"
ES256Expried string = "eyJhbGciOiJFUzI1NiIsImtpZCI6InAyNTYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.9AnXd2rZ6FirHZQAoabyL4xZNz0jr-3LmcV4-pFV3JrdtUT4386Mw5Qan125fUB-rZf_ZBlv0Bft2tWY149fyg"
RS256Allow string = "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MjAxOTY4NjQwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.iO0wPY91b_VNGUMZ1n-Ub-SRmEkDQMFLSi77z49tEzll3UZXwmBraP5udM_OPUAdk9ZO3dbb_fOgdcN9V1H9p5kiTr-l-pZTFTJHrPJj8wC519sYRcCk3wrZ9aXR5tNMwOsMdQb7waTBatDQLmHPWzAoTNBc8mwXkRcv1dmJLvsJgxyCl1I9CMOMPq0fYj1NBvaUDIdVSL1o7GGiriD8-0UIOmS72-I3mbaoCIyVb0h3wx7gnIW3zr0yYWaYoiIgmHLag-eEGxHp4-BjtCqcokU4QVMS91qpH7Mkl1iv2WHEkuDQRJ-nLzYGwXb7Dncx9K5tNWHJuZ-DihIU2oT0aA"
RS256Expried string = "eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIiwiYmFyIl0sImV4cCI6MTcwNDA2NzIwMCwiaXNzIjoiaGlncmVzcy10ZXN0IiwibmJmIjoxNzA0MDY3MjAwLCJzdWIiOiJoaWdyZXNzLXRlc3QifQ.jqzlhBPk9mmvtTT5aCYf-_5uXXSEU5bQ32fx78XeboCnjR9K1CsI4KYUIkXEX3bk66XJQUeSes7lz3gA4Yzkd-v9oADHTgpKnIxzv_5mD0_afIwEFjcalqVbSvCmro4PessQZDnmU7AIzoo3RPSqbmq8xbPVYUH9I-OO8aUu2ATd1HozgxJH1XnRU8k9KMkVW8XhvJXLKZJmnqe3Tu6pCU_tawFlBfBC4fAhMf0yX2CGE0ABAHubcdiI6JXObQmQQ9Or2a-g2a8g_Bw697PoPOsAn0YpTrHst9GcyTpkbNTAq9X8fc5EM7hiDM1FGeMYcaQTdMnOh4HBhP0p4YEhvA"
)
func init() {
Register(WasmPluginsJWTAuthAllow)
Register(WasmPluginsJWTAuthExpried)
Register(WasmPluginsJWTAuthDeny)
Register(WasmPluginsJWTAuthSingleConsumer)
}
var WasmPluginsJWTAuthAllow = suite.ConformanceTest{
ShortName: "WasmPluginsJWTAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth WASM plugin.",
Manifests: []string{"tests/go-wasm-jwt-auth-allow.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "1. Default header with ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + ES256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. Default header with RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + RS256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "3. Default params with ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + ES256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "4. Default params with RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + RS256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "5. Custom header with ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + ES256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "6. Custom header with RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + RS256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Custom params with ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + ES256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "8. Custom params with RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + RS256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "9. Custom cookies with ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + ES256Allow},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "10. Custom cookies with RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + RS256Allow},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins jwt-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}
var WasmPluginsJWTAuthExpried = suite.ConformanceTest{
ShortName: "WasmPluginsJWTAuthExpried",
Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth WASM plugin.",
Manifests: []string{"tests/go-wasm-jwt-auth-deny.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "1. Default header with expried ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + ES256Expried},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. Default header with expried RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + RS256Expried},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "3. Default params with expried ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + ES256Expried,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "4. Default params with expried RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + RS256Expried,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "5. Custom header with expried ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + ES256Expried},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "6. Custom header with expried RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + RS256Expried},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Custom params with expried ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + ES256Expried,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "8. Custom params with expried RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + RS256Expried,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "9. Custom cookies with expried ES256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + ES256Expried},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "10. Custom cookies with expried RS256",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + RS256Expried},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins jwt-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}
var WasmPluginsJWTAuthDeny = suite.ConformanceTest{
ShortName: "WasmPluginsJWTAuthDeny",
Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth WASM plugin.",
Manifests: []string{"tests/go-wasm-jwt-auth-deny.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "1. Default header with RS256 but unauthorized consumer",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + RS256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. No token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "3. Default header with no token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + ""},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "4. Default params with no token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + "",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "5. Custom header with no token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + ""},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "6. Custom params with no token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + "",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Custom cookies with no token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + ""},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "8. Default header with fake token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + "faketoken"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "9. Default params with fake token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + "faketoken",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "10. Custom header with fake token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"jwt": "Bearer " + "faketoken"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "11. Custom params with fake token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?jwt_token=" + "faketoken",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "12. Custom cookies with fake token",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
Headers: map[string]string{"Cookie": "jwt_token=" + "faketoken"},
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins jwt-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}
var WasmPluginsJWTAuthSingleConsumer = suite.ConformanceTest{
ShortName: "WasmPluginsJWTAuthSingleConsumer",
Description: "The Ingress in the higress-conformance-infra namespace test the jwt-auth WASM plugin.",
Manifests: []string{"tests/go-wasm-jwt-auth-single-consumer.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "1. Default hedaer with ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + ES256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "2. Default hedaer with expried ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + ES256Expried},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "3. Default hedaer with fake token by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + "faketoken"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "4. No token by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "5. Default header with RS256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"Authorization": "Bearer " + RS256Allow},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "6. Default params with ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + ES256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "7. Default params with expried ES256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + ES256Expried,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "8. Default params with fake token by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + "faketoken",
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 401,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
TestCaseName: "9. Default params with RS256 by single consumer_EC",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info?access_token=" + RS256Allow,
UnfollowRedirect: true,
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins jwt-auth", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -26,7 +26,7 @@ func init() {
}
var WasmPluginsJwtAuth = suite.ConformanceTest{
ShortName: "WasmPluginsJwtAuth",
ShortName: "WasmPluginsSimpleJwtAuth",
Description: "The Ingress in the higress-conformance-infra namespace test the simple-jwt-auth wasmplugins.",
Manifests: []string{"tests/go-wasm-simple-jwt-auth.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},