Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM scratch
COPY main.wasm plugin.wasm
5 changes: 5 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/Makefile
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions plugins/wasm-go/extensions/jwt-auth/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
34 changes: 34 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/config/checker.go
Original file line number Diff line number Diff line change
@@ -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 changes: 125 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/config/config.go
Original file line number Diff line number Diff line change
@@ -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"`
}
143 changes: 143 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/config/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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"
)

// Enabled 插件是否至少在一个 domain 或 route 上生效
var Enabled bool

// ParseGlobalConfig 从wrapper提供的配置中解析并转换到插件运行时需要使用的配置。
// 此处解析的是全局配置,域名和路由级配置由 ParseRuleConfig 负责。
func ParseGlobalConfig(json gjson.Result, config *JWTAuthConfig, log wrapper.Log) error {
Enabled = 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")
}

// GlobalAuth 为 true 时代表插件全局启用,需要标记 enabled 为 true
johnlanni marked this conversation as resolved.
Show resolved Hide resolved
if config.GlobalAuth != nil && *config.GlobalAuth {
Enabled = true
}

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())
}

Enabled = 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 changes: 22 additions & 0 deletions plugins/wasm-go/extensions/jwt-auth/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
Loading
Loading