// 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" ) // 测试配置:基本配置 var basicConfig = func() json.RawMessage { data, _ := json.Marshal(map[string]interface{}{ "redis": map[string]interface{}{ "service_name": "redis.static", "service_port": 80, }, "force_nonce": true, "nonce_header": "X-Higress-Nonce", "nonce_ttl": 900, "nonce_min_length": 8, "nonce_max_length": 128, "validate_base64": true, "reject_code": 429, "reject_msg": "Replay Attack Detected", }) return data }() // 测试配置:自定义配置 var customConfig = func() json.RawMessage { data, _ := json.Marshal(map[string]interface{}{ "redis": map[string]interface{}{ "service_name": "custom-redis.svc.cluster.local", "service_port": 6379, "username": "admin", "password": "password123", "timeout": 2000, "database": 1, "key_prefix": "custom-prefix", }, "force_nonce": false, "nonce_header": "X-Custom-Nonce", "nonce_ttl": 1800, "nonce_min_length": 16, "nonce_max_length": 64, "validate_base64": false, "reject_code": 400, "reject_msg": "Custom Reject Message", }) return data }() // 测试配置:最小配置(使用默认值) var minimalConfig = func() json.RawMessage { data, _ := json.Marshal(map[string]interface{}{ "redis": map[string]interface{}{ "service_name": "redis.static", }, }) return data }() // 测试配置:无效配置(缺少 Redis 配置) var invalidConfig = func() json.RawMessage { data, _ := json.Marshal(map[string]interface{}{ "force_nonce": true, "nonce_header": "X-Higress-Nonce", }) return data }() // 测试配置:无效配置(空的 Redis 服务名) var invalidRedisConfig = func() json.RawMessage { data, _ := json.Marshal(map[string]interface{}{ "redis": map[string]interface{}{ "service_name": "", }, }) return data }() func TestParseConfig(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { // 测试基本配置解析 t.Run("basic config", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) config, err := host.GetMatchConfig() require.NoError(t, err) require.NotNil(t, config) }) // 测试自定义配置解析 t.Run("custom config", func(t *testing.T) { host, status := test.NewTestHost(customConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) config, err := host.GetMatchConfig() require.NoError(t, err) require.NotNil(t, config) }) // 测试最小配置解析(使用默认值) t.Run("minimal config", func(t *testing.T) { host, status := test.NewTestHost(minimalConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) config, err := host.GetMatchConfig() require.NoError(t, err) require.NotNil(t, config) }) // 测试无效配置(缺少 Redis 配置) t.Run("invalid config - missing redis", func(t *testing.T) { host, status := test.NewTestHost(invalidConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusFailed, status) }) // 测试无效配置(空的 Redis 服务名) t.Run("invalid config - empty redis service name", func(t *testing.T) { host, status := test.NewTestHost(invalidRedisConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusFailed, status) }) }) } func TestOnHttpRequestHeaders(t *testing.T) { test.RunTest(t, func(t *testing.T) { // 测试强制 nonce 模式 - 缺少 nonce 头 t.Run("force nonce - missing nonce header", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, }) require.Equal(t, types.ActionPause, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse) require.Equal(t, uint32(400), localResponse.StatusCode) require.Equal(t, "Missing Required Header", string(localResponse.Data)) host.CompleteHttp() }) // 测试强制 nonce 模式 - 有效的 nonce t.Run("force nonce - valid nonce", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 模拟 Redis 客户端成功设置 nonce host.SetProperty([]string{"redis", "client", "mock"}, []byte("success")) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", "dGVzdC1ub25jZS12YWx1ZQ=="}, // base64 encoded "test-nonce-value" }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) host.CompleteHttp() }) // 测试非强制 nonce 模式 - 缺少 nonce 头(应该通过) t.Run("non-force nonce - missing nonce header", func(t *testing.T) { host, status := test.NewTestHost(customConfig) 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) localResponse := host.GetLocalResponse() require.Nil(t, localResponse, "Request should pass through when nonce is not required") host.CompleteHttp() }) // 测试无效的 nonce 长度(太短) t.Run("invalid nonce - too short", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", "short"}, // 长度只有 5,小于最小值 8 }) require.Equal(t, types.ActionPause, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse) require.Equal(t, uint32(400), localResponse.StatusCode) require.Equal(t, "Invalid Nonce", string(localResponse.Data)) host.CompleteHttp() }) // 测试无效的 nonce 长度(太长) t.Run("invalid nonce - too long", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 创建一个超过最大长度的 nonce longNonce := "a" for i := 0; i < 130; i++ { longNonce += "a" } action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", longNonce}, }) require.Equal(t, types.ActionPause, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse) require.Equal(t, uint32(400), localResponse.StatusCode) require.Equal(t, "Invalid Nonce", string(localResponse.Data)) host.CompleteHttp() }) // 测试无效的 base64 格式(当启用验证时) t.Run("invalid nonce - invalid base64 format", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", "invalid-base64!@#"}, // 包含无效字符 }) require.Equal(t, types.ActionPause, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse) require.Equal(t, uint32(400), localResponse.StatusCode) require.Equal(t, "Invalid Nonce", string(localResponse.Data)) host.CompleteHttp() }) // 测试自定义 nonce 头名称 t.Run("custom nonce header name", func(t *testing.T) { host, status := test.NewTestHost(customConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Custom-Nonce", "dGVzdC1ub25jZS12YWx1ZQ=="}, // 使用自定义头名称 }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) host.CompleteHttp() }) // 测试有效的 nonce(长度在范围内,格式正确) t.Run("valid nonce - correct format and length", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 模拟 Redis 客户端成功设置 nonce host.SetProperty([]string{"redis", "client", "mock"}, []byte("success")) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", "dGVzdC1ub25jZS12YWx1ZQ=="}, // base64 encoded "test-nonce-value" }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) host.CompleteHttp() }) }) } func TestValidateNonce(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { // 测试 nonce 长度验证 t.Run("nonce length validation", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 测试太短的 nonce shortNonce := "short" action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", shortNonce}, }) require.Equal(t, types.ActionPause, action) // 测试太长的 nonce longNonce := "a" for i := 0; i < 130; i++ { longNonce += "a" } action = host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", longNonce}, }) require.Equal(t, types.ActionPause, action) // 测试长度在范围内的 nonce validNonce := "dGVzdC1ub25jZS12YWx1ZQ==" // base64 encoded "test-nonce-value" action = host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", validNonce}, }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) }) // 测试 base64 格式验证 t.Run("base64 format validation", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 测试无效的 base64 格式 invalidBase64 := "invalid-base64!@#" action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", invalidBase64}, }) require.Equal(t, types.ActionPause, action) // 测试有效的 base64 格式 validBase64 := "dGVzdC1ub25jZS12YWx1ZQ==" action = host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", validBase64}, }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) }) }) } func TestCompleteFlow(t *testing.T) { test.RunTest(t, func(t *testing.T) { t.Run("complete request flow", func(t *testing.T) { host, status := test.NewTestHost(basicConfig) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 1. 处理请求头 action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/test"}, {":method", "POST"}, {"X-Higress-Nonce", "dGVzdC1ub25jZS12YWx1ZQ=="}, }) // 由于 Redis 操作是异步的,这里会返回 ActionPause require.Equal(t, types.ActionPause, action) host.CompleteHttp() }) }) }