package main import ( "crypto/hmac" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/json" "fmt" "hash" "strings" "testing" "time" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" "github.com/higress-group/wasm-go/pkg/test" "github.com/stretchr/testify/require" ) // 生成有效的 HMAC 签名 - 与插件中的 generateSigningString 方法保持一致 func generateValidSignature(accessKey, secretKey, algorithm, requestMethod, requestURI string, headers []string, headerValues map[string]string) string { // 构造签名字符串,严格按照插件中的 generateSigningString 方法实现 var signingStringItems []string // 第一行是 keyId signingStringItems = append(signingStringItems, accessKey) if len(headers) > 0 { for _, h := range headers { if h == "@request-target" { // 注意:这里使用原始请求方法和URI requestTarget := fmt.Sprintf("%s %s", requestMethod, requestURI) signingStringItems = append(signingStringItems, requestTarget) } else { if value, ok := headerValues[h]; ok { signingStringItems = append(signingStringItems, fmt.Sprintf("%s: %s", h, value)) } } } } // 签名字符串需要以换行符结尾 signingString := strings.Join(signingStringItems, "\n") + "\n" // 生成 HMAC 签名 var mac hash.Hash switch algorithm { case "hmac-sha1": mac = hmac.New(sha1.New, []byte(secretKey)) case "hmac-sha256": mac = hmac.New(sha256.New, []byte(secretKey)) case "hmac-sha512": mac = hmac.New(sha512.New, []byte(secretKey)) default: mac = hmac.New(sha256.New, []byte(secretKey)) } mac.Write([]byte(signingString)) signature := mac.Sum(nil) return base64.StdEncoding.EncodeToString(signature) } // 生成 Authorization 头 func generateAuthorizationHeader(accessKey, secretKey, algorithm, requestMethod, requestURI string, headers []string, headerValues map[string]string) string { signature := generateValidSignature(accessKey, secretKey, algorithm, requestMethod, requestURI, headers, headerValues) header := fmt.Sprintf(`Signature keyId="%s",algorithm="%s",signature="%s"`, accessKey, algorithm, signature) if len(headers) > 0 { header += fmt.Sprintf(`,headers="%s"`, strings.Join(headers, " ")) } return header } // 辅助函数:生成有效的认证头 func generateValidAuthHeaderWithDate(dateStr, method, path string) string { headerValues := map[string]string{ "date": dateStr, } return generateAuthorizationHeader("ak1", "sk1", "hmac-sha256", method, path, []string{"@request-target", "date"}, headerValues) } // 通用测试配置生成函数 func createConfig(consumers []map[string]interface{}, extra map[string]interface{}) json.RawMessage { config := map[string]interface{}{ "consumers": consumers, // 设置 clock_skew 为 0 来跳过时钟偏差校验 "clock_skew": 0, } for k, v := range extra { config[k] = v } data, _ := json.Marshal(config) return data } func TestParseGlobalConfig(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { tests := []struct { name string config json.RawMessage expected bool }{ { "basic global config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{}, ), true, }, { "global auth true config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, ), true, }, { "global auth false config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": false, }, ), true, }, { "clock skew config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "clock_skew": 600, }, ), true, }, { "algorithm config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "allowed_algorithms": []string{"hmac-sha256"}, }, ), true, }, { "signed headers config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "signed_headers": []string{"host", "date"}, }, ), true, }, { "anonymous consumer config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "anonymous_consumer": "anonymous", }, ), true, }, { "invalid config - missing consumers", createConfig( []map[string]interface{}{}, map[string]interface{}{ "global_auth": false, }, ), false, }, { "invalid config - empty consumers", createConfig( []map[string]interface{}{}, map[string]interface{}{ "consumers": []map[string]interface{}{}, }, ), false, }, { "invalid config - missing access_key", createConfig( []map[string]interface{}{ { "name": "consumer1", "secret_key": "sk1", }, }, map[string]interface{}{}, ), false, }, { "invalid config - missing secret_key", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", }, }, map[string]interface{}{}, ), false, }, { "invalid config - duplicate access_key", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak1", "secret_key": "sk2", }, }, map[string]interface{}{}, ), false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { host, status := test.NewTestHost(tt.config) defer host.Reset() if tt.expected { require.Equal(t, types.OnPluginStartStatusOK, status) } else { require.Equal(t, types.OnPluginStartStatusFailed, status) } }) } }) } func TestParseOverrideRuleConfig(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { tests := []struct { name string config json.RawMessage expected bool }{ { "route auth config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "allow": []string{"consumer1"}, }, ), true, }, { "domain auth config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "allow": []string{"consumer2"}, }, ), true, }, { "invalid config - empty allow list", createConfig( []map[string]interface{}{}, map[string]interface{}{ "allow": []string{}, }, ), false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { host, status := test.NewTestHost(tt.config) defer host.Reset() if tt.expected { require.Equal(t, types.OnPluginStartStatusOK, status) } else { require.Equal(t, types.OnPluginStartStatusFailed, status) } }) } }) } func TestParseRuleConfig(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { tests := []struct { name string config json.RawMessage expected bool }{ { "route level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_route_": []string{"route-a", "route-b"}, "allow": []string{"consumer1"}, }, { "_match_route_": []string{"route-c"}, "allow": []string{"consumer2"}, }, }, }, ), true, }, { "domain level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_domain_": []string{"*.example.com", "test.com"}, "allow": []string{"consumer2"}, }, { "_match_domain_": []string{"api.example.com"}, "allow": []string{"consumer1"}, }, }, }, ), true, }, { "service level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_service_": []string{"service-a:8080", "service-b"}, "allow": []string{"consumer1"}, }, { "_match_service_": []string{"service-b:9090"}, "allow": []string{"consumer2"}, }, }, }, ), true, }, { "route prefix level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_route_prefix_": []string{"api-", "web-"}, "allow": []string{"consumer1"}, }, { "_match_route_prefix_": []string{"admin-", "internal-"}, "allow": []string{"consumer2"}, }, }, }, ), true, }, { "route and service level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_route_": []string{"route-a"}, "_match_service_": []string{"service-a:8080"}, "allow": []string{"consumer1"}, }, { "_match_route_": []string{"route-b"}, "_match_service_": []string{"service-b:9090"}, "allow": []string{"consumer2"}, }, }, }, ), true, }, { "mixed level config", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, { "name": "consumer3", "access_key": "ak3", "secret_key": "sk3", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_route_": []string{"api-route"}, "allow": []string{"consumer1"}, }, { "_match_domain_": []string{"*.example.com"}, "allow": []string{"consumer2"}, }, { "_match_service_": []string{"internal-service:8080"}, "allow": []string{"consumer3"}, }, { "_match_route_prefix_": []string{"web-"}, "allow": []string{"consumer1", "consumer2"}, }, }, }, ), true, }, { "invalid rule config - missing match conditions", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "allow": []string{"consumer1"}, // 缺少匹配条件 }, }, }, ), false, }, { "invalid rule config - empty match conditions", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "_rules_": []map[string]interface{}{ { "_match_route_": []string{}, "allow": []string{"consumer1"}, }, }, }, ), false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { host, status := test.NewTestHost(tt.config) defer host.Reset() if tt.expected { require.Equal(t, types.OnPluginStartStatusOK, status) } else { require.Equal(t, types.OnPluginStartStatusFailed, status) } }) } }) } func TestOnHttpRequestHeaders(t *testing.T) { test.RunTest(t, func(t *testing.T) { tests := []struct { name string config json.RawMessage headers [][2]string expectContinue bool expectError string }{ { "missing authorization header", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{}, ), [][2]string{{":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}}, true, `{"message":"client request can't be validated: missing Authorization header"}`, }, { "empty authorization header", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{}, ), [][2]string{{":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", ""}}, true, `{"message":"client request can't be validated: missing Authorization header"}`, }, { "invalid authorization format - missing signature prefix", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, ), [][2]string{{":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", "Bearer token123"}}, true, `{"message":"client request can't be validated: Authorization header does not start with 'Signature '"}`, }, { "invalid authorization format - invalid signature format", createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, ), [][2]string{{":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", "Signature invalid-format"}}, true, `{"message":"client request can't be validated: keyId or signature missing"}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { host, status := test.NewTestHost(tt.config) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders(tt.headers) require.Equal(t, types.ActionContinue, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse) require.Equal(t, uint32(401), localResponse.StatusCode) require.Equal(t, tt.expectError, string(localResponse.Data)) host.CompleteHttp() }) } // 测试有效的凭证情况 t.Run("valid credentials - global auth true, no allow config", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("ak1", "sk1", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) require.Nil(t, host.GetLocalResponse(), "Valid credentials should be accepted") host.CompleteHttp() }) // 测试无效的凭证(未配置的 access_key) t.Run("invalid credential - not configured access_key", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("unknown_ak", "sk", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse, "Invalid credentials should be rejected") require.Equal(t, uint32(401), localResponse.StatusCode) require.Equal(t, `{"message":"client request can't be validated: Invalid keyId"}`, string(localResponse.Data)) host.CompleteHttp() }) // 测试无效的签名(错误的签名) t.Run("invalid signature - wrong signature", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("ak1", "sk1", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) // 故意修改签名 authHeader = strings.Replace(authHeader, "signature=", "signature=wrong", 1) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse, "Invalid signature should be rejected") require.Equal(t, uint32(401), localResponse.StatusCode) require.Equal(t, `{"message":"client request can't be validated: keyId or signature missing"}`, string(localResponse.Data)) host.CompleteHttp() }) // 测试有效的凭证(全局认证关闭,有 allow 配置) t.Run("valid credentials - global auth false, with allow config", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "allow": []string{"consumer1"}, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("ak1", "sk1", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) require.Nil(t, host.GetLocalResponse(), "Valid credentials should be accepted") host.CompleteHttp() }) // 测试有效的凭证但不在 allow 列表中的情况 t.Run("valid credentials but not in allow list", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, { "name": "consumer2", "access_key": "ak2", "secret_key": "sk2", }, }, map[string]interface{}{ "allow": []string{"consumer1"}, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("ak2", "sk2", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) require.Nil(t, host.GetLocalResponse(), "Valid credentials should be accepted when not in allow list") host.CompleteHttp() }) // 测试匿名消费者配置 t.Run("anonymous consumer config", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "anonymous_consumer": "anonymous", }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, }) require.Equal(t, types.ActionContinue, action) require.Nil(t, host.GetLocalResponse(), "Request without credentials should be accepted with anonymous consumer") host.CompleteHttp() }) }) } func TestCompleteFlow(t *testing.T) { test.RunTest(t, func(t *testing.T) { t.Run("complete hmac auth flow", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "global_auth": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 1. 测试缺少认证信息的情况 action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, }) require.Equal(t, types.ActionContinue, action) localResponse := host.GetLocalResponse() require.NotNil(t, localResponse, "Request without credentials should be rejected") require.Equal(t, uint32(401), localResponse.StatusCode) require.Equal(t, `{"message":"client request can't be validated: missing Authorization header"}`, string(localResponse.Data)) host.CompleteHttp() // 2. 测试有效认证的情况 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") headerValues := map[string]string{ "date": fixedTime, } authHeader := generateAuthorizationHeader("ak1", "sk1", "hmac-sha256", "GET", "/api/test", []string{"@request-target"}, headerValues) action = host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "GET"}, {"authorization", authHeader}, {"date", fixedTime}, }) require.Equal(t, types.ActionContinue, action) requestHeaders := host.GetRequestHeaders() require.True(t, test.HasHeaderWithValue(requestHeaders, "X-Mse-Consumer", "consumer1")) require.Nil(t, host.GetLocalResponse(), "Valid credentials should be accepted") host.CompleteHttp() }) }) } func TestOnHttpRequestBody(t *testing.T) { test.RunTest(t, func(t *testing.T) { // 测试有效的请求体和摘要 t.Run("valid request body with correct digest", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "validate_request_body": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 准备空请求体的摘要 validBody := []byte(`{"name": "test", "value": 123}`) correctDigest := calculateBodyDigest(validBody) // 使用固定的时间值确保一致性 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") // 生成与请求头一致的签名 authHeader := generateValidAuthHeaderWithDate(fixedTime, "POST", "/api/test") // 先处理请求头 host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "POST"}, {"authorization", authHeader}, {"date", fixedTime}, {"digest", correctDigest}, // 使用正确的摘要 {"content-type", "application/json"}, }) // 测试有效的请求体 action := host.CallOnHttpRequestBody(validBody) require.Equal(t, types.ActionContinue, action, "Valid body with correct digest should be accepted") require.Equal(t, types.ActionContinue, host.GetHttpStreamAction(), "Stream action should be continue") localResponse := host.GetLocalResponse() require.Nil(t, localResponse, "Valid body with correct digest should not be rejected") host.CompleteHttp() }) // 测试无效的摘要 t.Run("invalid digest", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "validate_request_body": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 使用固定的时间值确保一致性 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") // 生成与请求头一致的签名 authHeader := generateValidAuthHeaderWithDate(fixedTime, "POST", "/api/test") // 先调用头部处理 host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "POST"}, {"authorization", authHeader}, {"date", fixedTime}, {"digest", "SHA-256=invalid-digest"}, {"content-type", "application/json"}, }) // 测试请求体但摘要不匹配 body := []byte(`{"name": "test", "value": 123}`) action := host.CallOnHttpRequestBody(body) require.Equal(t, types.ActionContinue, action, "Request with invalid digest should be rejected") require.Equal(t, types.ActionContinue, host.GetHttpStreamAction(), "Stream action should be continue") localResponse := host.GetLocalResponse() require.NotNil(t, localResponse, "Request with invalid digest should be rejected") require.Equal(t, uint32(401), localResponse.StatusCode) require.Contains(t, string(localResponse.Data), "Invalid digest", "Error message should indicate invalid digest") host.CompleteHttp() }) // 测试缺少摘要头 t.Run("missing digest header", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "validate_request_body": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 使用固定的时间值确保一致性 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") // 生成与请求头一致的签名 authHeader := generateValidAuthHeaderWithDate(fixedTime, "POST", "/api/test") // 先调用头部处理,但不设置digest头 host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "POST"}, {"authorization", authHeader}, {"date", fixedTime}, // 故意不设置digest头 {"content-type", "application/json"}, }) // 测试请求体但缺少摘要头 body := []byte(`{"name": "test", "value": 123}`) action := host.CallOnHttpRequestBody(body) require.Equal(t, types.ActionContinue, action, "Request without digest header should be rejected") require.Equal(t, types.ActionContinue, host.GetHttpStreamAction(), "Stream action should be continue") localResponse := host.GetLocalResponse() require.NotNil(t, localResponse, "Request without digest header should be rejected") require.Equal(t, uint32(401), localResponse.StatusCode) require.Contains(t, string(localResponse.Data), "Invalid digest", "Error message should indicate invalid digest") host.CompleteHttp() }) // 测试未启用请求体验证 t.Run("body validation disabled", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "validate_request_body": false, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 使用固定的时间值确保一致性 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") // 生成与请求头一致的签名 authHeader := generateValidAuthHeaderWithDate(fixedTime, "POST", "/api/test") // 先调用头部处理 action := host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "POST"}, {"authorization", authHeader}, {"date", fixedTime}, {"content-type", "application/json"}, }) // 由于禁用了请求体验证,应该直接继续而不等待请求体 require.Equal(t, types.ActionContinue, action, "Should continue immediately when validate_request_body is false") host.CompleteHttp() }) // 测试空请求体 t.Run("empty request body", func(t *testing.T) { host, status := test.NewTestHost(createConfig( []map[string]interface{}{ { "name": "consumer1", "access_key": "ak1", "secret_key": "sk1", }, }, map[string]interface{}{ "validate_request_body": true, }, )) defer host.Reset() require.Equal(t, types.OnPluginStartStatusOK, status) // 使用固定的时间值确保一致性 fixedTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") // 准备空请求体的摘要 emptyBody := []byte("") correctDigest := calculateBodyDigest(emptyBody) // 生成与请求头一致的签名 authHeader := generateValidAuthHeaderWithDate(fixedTime, "POST", "/api/test") // 先调用头部处理 host.CallOnHttpRequestHeaders([][2]string{ {":authority", "example.com"}, {":path", "/api/test"}, {":method", "POST"}, {"authorization", authHeader}, {"date", fixedTime}, {"digest", correctDigest}, // 空字符串的SHA-256摘要 {"content-type", "application/json"}, }) // 测试空请求体 action := host.CallOnHttpRequestBody(emptyBody) require.Equal(t, types.ActionContinue, action, "Empty body with correct digest should be accepted") require.Equal(t, types.ActionContinue, host.GetHttpStreamAction(), "Stream action should be continue") localResponse := host.GetLocalResponse() require.Nil(t, localResponse, "Empty body with correct digest should not be rejected") host.CompleteHttp() }) }) }