mirror of
https://github.com/alibaba/higress.git
synced 2026-03-01 23:20:52 +08:00
581 lines
17 KiB
Go
581 lines
17 KiB
Go
// 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 (
|
||
"encoding/json"
|
||
"testing"
|
||
|
||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||
"github.com/higress-group/wasm-go/pkg/test"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// 测试配置:基本 key-auth 配置
|
||
var basicKeyAuthConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
{
|
||
"name": "consumer2",
|
||
"credential": "token2",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key", "apikey"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:全局认证关闭
|
||
var globalAuthFalseConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": false,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:从 query 参数获取 key
|
||
var queryKeyConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{"apikey"},
|
||
"in_header": false,
|
||
"in_query": true,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:多个 key 来源
|
||
var multipleKeysConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key", "apikey", "authorization"},
|
||
"in_header": true,
|
||
"in_query": true,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 缺少 keys
|
||
var invalidNoKeysConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 空的 keys
|
||
var invalidEmptyKeysConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 缺少 consumers
|
||
var invalidNoConsumersConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 空的 consumers
|
||
var invalidEmptyConsumersConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{},
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 缺少 in_query 和 in_header
|
||
var invalidNoSourceConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key"},
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:无效配置 - 重复的 credential
|
||
var invalidDuplicateCredentialConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
{
|
||
"name": "consumer2",
|
||
"credential": "token1", // 重复的 credential
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:规则配置 - 带 allow 列表
|
||
var ruleConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
{
|
||
"name": "consumer2",
|
||
"credential": "token2",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
"_rules_": []map[string]interface{}{
|
||
{
|
||
"_match_route_": []string{"test-route"},
|
||
"allow": []string{"consumer1"},
|
||
},
|
||
},
|
||
})
|
||
return data
|
||
}()
|
||
|
||
// 测试配置:规则配置 - 空的 allow 列表
|
||
var invalidRuleConfig = func() json.RawMessage {
|
||
data, _ := json.Marshal(map[string]interface{}{
|
||
"consumers": []map[string]interface{}{
|
||
{
|
||
"name": "consumer1",
|
||
"credential": "token1",
|
||
},
|
||
},
|
||
"keys": []string{"x-api-key"},
|
||
"in_header": true,
|
||
"in_query": false,
|
||
"global_auth": true,
|
||
"_rules_": []map[string]interface{}{
|
||
{
|
||
"_match_route_": []string{"test-route"},
|
||
"allow": []string{},
|
||
},
|
||
},
|
||
})
|
||
return data
|
||
}()
|
||
|
||
func TestParseGlobalConfig(t *testing.T) {
|
||
test.RunGoTest(t, func(t *testing.T) {
|
||
// 测试基本 key-auth 配置解析
|
||
t.Run("basic key-auth config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(basicKeyAuthConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
|
||
keyAuthConfig := config.(*KeyAuthConfig)
|
||
// 注意:由于字段是私有的,我们只能验证配置能够成功解析
|
||
require.NotNil(t, keyAuthConfig)
|
||
require.Len(t, keyAuthConfig.Keys, 2)
|
||
require.Equal(t, "x-api-key", keyAuthConfig.Keys[0])
|
||
require.Equal(t, "apikey", keyAuthConfig.Keys[1])
|
||
require.True(t, keyAuthConfig.InHeader)
|
||
require.False(t, keyAuthConfig.InQuery)
|
||
})
|
||
|
||
// 测试全局认证关闭配置
|
||
t.Run("global auth false config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(globalAuthFalseConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
|
||
keyAuthConfig := config.(*KeyAuthConfig)
|
||
// 注意:由于字段是私有的,我们只能验证配置能够成功解析
|
||
require.NotNil(t, keyAuthConfig)
|
||
require.Len(t, keyAuthConfig.Keys, 1)
|
||
require.Equal(t, "x-api-key", keyAuthConfig.Keys[0])
|
||
})
|
||
|
||
// 测试从 query 参数获取 key 的配置
|
||
t.Run("query key config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(queryKeyConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
|
||
keyAuthConfig := config.(*KeyAuthConfig)
|
||
require.NotNil(t, keyAuthConfig)
|
||
require.False(t, keyAuthConfig.InHeader)
|
||
require.True(t, keyAuthConfig.InQuery)
|
||
require.Len(t, keyAuthConfig.Keys, 1)
|
||
require.Equal(t, "apikey", keyAuthConfig.Keys[0])
|
||
})
|
||
|
||
// 测试多个 key 来源的配置
|
||
t.Run("multiple keys config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(multipleKeysConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
|
||
keyAuthConfig := config.(*KeyAuthConfig)
|
||
require.NotNil(t, keyAuthConfig)
|
||
require.True(t, keyAuthConfig.InHeader)
|
||
require.True(t, keyAuthConfig.InQuery)
|
||
require.Len(t, keyAuthConfig.Keys, 3)
|
||
require.Equal(t, "x-api-key", keyAuthConfig.Keys[0])
|
||
require.Equal(t, "apikey", keyAuthConfig.Keys[1])
|
||
require.Equal(t, "authorization", keyAuthConfig.Keys[2])
|
||
})
|
||
|
||
// 测试无效配置 - 缺少 keys
|
||
t.Run("invalid no keys config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidNoKeysConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
|
||
// 测试无效配置 - 空的 keys
|
||
t.Run("invalid empty keys config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidEmptyKeysConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
|
||
// 测试无效配置 - 缺少 consumers
|
||
t.Run("invalid no consumers config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidNoConsumersConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
|
||
// 测试无效配置 - 空的 consumers
|
||
t.Run("invalid empty consumers config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidEmptyConsumersConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
|
||
// 测试无效配置 - 缺少 in_query 和 in_header
|
||
t.Run("invalid no source config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidNoSourceConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
|
||
// 测试无效配置 - 重复的 credential
|
||
t.Run("invalid duplicate credential config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidDuplicateCredentialConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
})
|
||
}
|
||
|
||
func TestParseRuleConfig(t *testing.T) {
|
||
test.RunGoTest(t, func(t *testing.T) {
|
||
// 测试有效的规则配置
|
||
t.Run("valid rule config", func(t *testing.T) {
|
||
host, status := test.NewTestHost(ruleConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
config, err := host.GetMatchConfig()
|
||
require.NoError(t, err)
|
||
require.NotNil(t, config)
|
||
|
||
keyAuthConfig := config.(*KeyAuthConfig)
|
||
// 注意:由于配置解析逻辑的复杂性,我们只验证配置能够成功解析
|
||
require.NotNil(t, keyAuthConfig)
|
||
// allow 字段的解析可能需要更复杂的配置结构
|
||
})
|
||
|
||
// 测试无效的规则配置 - 空的 allow 列表
|
||
t.Run("invalid rule config - empty allow", func(t *testing.T) {
|
||
host, status := test.NewTestHost(invalidRuleConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusFailed, status)
|
||
})
|
||
})
|
||
}
|
||
|
||
func TestOnHTTPRequestHeaders(t *testing.T) {
|
||
test.RunTest(t, func(t *testing.T) {
|
||
// 测试全局认证开启 - 有效的 API key
|
||
t.Run("global auth true - valid api key", func(t *testing.T) {
|
||
host, status := test.NewTestHost(basicKeyAuthConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
// 设置有效的 API key 在请求头中
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
{"x-api-key", "token1"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.Nil(t, localResponse, "Valid API key should pass through")
|
||
|
||
// 验证是否添加了 X-Mse-Consumer 头
|
||
headers := host.GetRequestHeaders()
|
||
consumerHeaderFound := false
|
||
for _, header := range headers {
|
||
if header[0] == "x-mse-consumer" && header[1] == "consumer1" {
|
||
consumerHeaderFound = true
|
||
break
|
||
}
|
||
}
|
||
require.True(t, consumerHeaderFound, "X-Mse-Consumer header should be added")
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试全局认证开启 - 无效的 API key
|
||
t.Run("global auth true - invalid api key", func(t *testing.T) {
|
||
host, status := test.NewTestHost(basicKeyAuthConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
{"x-api-key", "invalid-token"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.NotNil(t, localResponse, "Invalid API key should be rejected")
|
||
require.Equal(t, uint32(403), localResponse.StatusCode) // Forbidden
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试全局认证开启 - 缺少 API key
|
||
t.Run("global auth true - missing api key", func(t *testing.T) {
|
||
host, status := test.NewTestHost(basicKeyAuthConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.NotNil(t, localResponse, "Missing API key should be rejected")
|
||
require.Equal(t, uint32(401), localResponse.StatusCode) // Unauthorized
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试全局认证开启 - 多个 API key(应该被拒绝)
|
||
t.Run("global auth true - multiple api keys", func(t *testing.T) {
|
||
host, status := test.NewTestHost(basicKeyAuthConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
{"x-api-key", "token1"},
|
||
{"apikey", "token2"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.NotNil(t, localResponse, "Multiple API keys should be rejected")
|
||
require.Equal(t, uint32(401), localResponse.StatusCode) // Unauthorized
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试全局认证关闭 - 无 allow 列表(直接放行)
|
||
t.Run("global auth false - no allow list", func(t *testing.T) {
|
||
host, status := test.NewTestHost(globalAuthFalseConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.Nil(t, localResponse, "No auth required should pass through")
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试从 query 参数获取 API key
|
||
t.Run("query api key", func(t *testing.T) {
|
||
host, status := test.NewTestHost(queryKeyConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
// 设置包含 API key 的查询参数
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test?apikey=token1"},
|
||
{":method", "GET"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.Nil(t, localResponse, "Valid API key in query should pass through")
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试从 query 参数获取 API key - 无效的 key
|
||
t.Run("query api key - invalid", func(t *testing.T) {
|
||
host, status := test.NewTestHost(queryKeyConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test?apikey=invalid-token"},
|
||
{":method", "GET"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.NotNil(t, localResponse, "Invalid API key in query should be rejected")
|
||
require.Equal(t, uint32(403), localResponse.StatusCode) // Forbidden
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
|
||
// 测试从 query 参数获取 API key - 缺少 key
|
||
t.Run("query api key - missing", func(t *testing.T) {
|
||
host, status := test.NewTestHost(queryKeyConfig)
|
||
defer host.Reset()
|
||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||
|
||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||
{":authority", "example.com"},
|
||
{":path", "/test"},
|
||
{":method", "GET"},
|
||
})
|
||
|
||
require.Equal(t, types.ActionContinue, action)
|
||
require.Equal(t, types.ActionContinue, host.GetHttpStreamAction())
|
||
|
||
localResponse := host.GetLocalResponse()
|
||
require.NotNil(t, localResponse, "Missing API key in query should be rejected")
|
||
require.Equal(t, uint32(401), localResponse.StatusCode) // Unauthorized
|
||
|
||
host.CompleteHttp()
|
||
})
|
||
})
|
||
}
|