Files
higress/plugins/wasm-go/extensions/key-auth/main_test.go

581 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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()
})
})
}