mirror of
https://github.com/alibaba/higress.git
synced 2026-03-15 22:30:47 +08:00
feat(plugin): implement golang version of plugin jwt-auth (#743)
Signed-off-by: Ink33 <Ink33@smlk.org>
This commit is contained in:
2
plugins/wasm-go/extensions/jwt-auth/Dockerfile
Normal file
2
plugins/wasm-go/extensions/jwt-auth/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM scratch
|
||||
COPY main.wasm plugin.wasm
|
||||
5
plugins/wasm-go/extensions/jwt-auth/Makefile
Normal file
5
plugins/wasm-go/extensions/jwt-auth/Makefile
Normal 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
|
||||
1
plugins/wasm-go/extensions/jwt-auth/VERSION
Normal file
1
plugins/wasm-go/extensions/jwt-auth/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.1.0
|
||||
34
plugins/wasm-go/extensions/jwt-auth/config/checker.go
Normal file
34
plugins/wasm-go/extensions/jwt-auth/config/checker.go
Normal 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
|
||||
}
|
||||
125
plugins/wasm-go/extensions/jwt-auth/config/config.go
Normal file
125
plugins/wasm-go/extensions/jwt-auth/config/config.go
Normal 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"`
|
||||
}
|
||||
138
plugins/wasm-go/extensions/jwt-auth/config/parser.go
Normal file
138
plugins/wasm-go/extensions/jwt-auth/config/parser.go
Normal 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
|
||||
}
|
||||
22
plugins/wasm-go/extensions/jwt-auth/go.mod
Normal file
22
plugins/wasm-go/extensions/jwt-auth/go.mod
Normal 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
|
||||
)
|
||||
71
plugins/wasm-go/extensions/jwt-auth/go.sum
Normal file
71
plugins/wasm-go/extensions/jwt-auth/go.sum
Normal 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=
|
||||
4
plugins/wasm-go/extensions/jwt-auth/go.work.sum
Normal file
4
plugins/wasm-go/extensions/jwt-auth/go.work.sum
Normal 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=
|
||||
33
plugins/wasm-go/extensions/jwt-auth/handler/claims.go
Normal file
33
plugins/wasm-go/extensions/jwt-auth/handler/claims.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
146
plugins/wasm-go/extensions/jwt-auth/handler/extractor.go
Normal file
146
plugins/wasm-go/extensions/jwt-auth/handler/extractor.go
Normal 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, ";")
|
||||
}
|
||||
159
plugins/wasm-go/extensions/jwt-auth/handler/handler.go
Normal file
159
plugins/wasm-go/extensions/jwt-auth/handler/handler.go
Normal 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
|
||||
}
|
||||
212
plugins/wasm-go/extensions/jwt-auth/handler/verify.go
Normal file
212
plugins/wasm-go/extensions/jwt-auth/handler/verify.go
Normal 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
|
||||
// 无需额外调用verify,claims内部已进行验证
|
||||
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)},
|
||||
}
|
||||
}
|
||||
129
plugins/wasm-go/extensions/jwt-auth/handler/verify_test.go
Normal file
129
plugins/wasm-go/extensions/jwt-auth/handler/verify_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
49
plugins/wasm-go/extensions/jwt-auth/main.go
Normal file
49
plugins/wasm-go/extensions/jwt-auth/main.go
Normal 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),
|
||||
)
|
||||
}
|
||||
52
plugins/wasm-go/extensions/jwt-auth/option.yaml
Normal file
52
plugins/wasm-go/extensions/jwt-auth/option.yaml
Normal 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
|
||||
144
plugins/wasm-go/extensions/jwt-auth/test/jwt_test.go
Normal file
144
plugins/wasm-go/extensions/jwt-auth/test/jwt_test.go
Normal 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()
|
||||
}
|
||||
24
plugins/wasm-go/extensions/jwt-auth/test/jwts.json
Normal file
24
plugins/wasm-go/extensions/jwt-auth/test/jwts.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
plugins/wasm-go/extensions/jwt-auth/test/keys.json
Normal file
17
plugins/wasm-go/extensions/jwt-auth/test/keys.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
142
test/e2e/conformance/tests/go-wasm-jwt-auth-allow.yaml
Normal file
142
test/e2e/conformance/tests/go-wasm-jwt-auth-allow.yaml
Normal 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
|
||||
131
test/e2e/conformance/tests/go-wasm-jwt-auth-deny.yaml
Normal file
131
test/e2e/conformance/tests/go-wasm-jwt-auth-deny.yaml
Normal 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
|
||||
128
test/e2e/conformance/tests/go-wasm-jwt-auth-single-consumer.yaml
Normal file
128
test/e2e/conformance/tests/go-wasm-jwt-auth-single-consumer.yaml
Normal 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
|
||||
944
test/e2e/conformance/tests/go-wasm-jwt-auth.go
Normal file
944
test/e2e/conformance/tests/go-wasm-jwt-auth.go
Normal 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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user