mirror of
https://github.com/alibaba/higress.git
synced 2026-03-10 03:30:48 +08:00
feat: add request-validation plugin (#700)
Signed-off-by: sjcsjc123 <1401189096@qq.com>
This commit is contained in:
146
plugins/wasm-go/extensions/request-validation/README.md
Normal file
146
plugins/wasm-go/extensions/request-validation/README.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 功能说明
|
||||
`request-validation`插件用于提前验证向上游服务转发的请求。该插件使用`JSON Schema`机制进行数据验证,可以验证请求的body及header数据。
|
||||
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- |-----| -------- |
|
||||
|header_schema|object|选填| - |配置用于验证请求header的JSON Schema|
|
||||
|body_schema|object|选填| - |配置用于验证请求body的JSON Schema|
|
||||
|rejected_code|number|选填| 403 |配置请求被拒绝时返回的HTTP状态码|
|
||||
|rejected_msg|string|选填| - |配置请求被拒绝时返回的HTTP应答Body|
|
||||
|enable_swagger|bool|选填| false |配置是否开启swagger文档验证|
|
||||
|enable_oas3|bool|选填| false |配置是否开启OAS3文档验证|
|
||||
|
||||
**校验规则对header和body是一样的,下面以body为例说明**
|
||||
|
||||
# 配置示例
|
||||
|
||||
## 枚举(Enum)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- enum_payload
|
||||
properties:
|
||||
enum_payload:
|
||||
type: string
|
||||
enum:
|
||||
- "enum_string_1"
|
||||
- "enum_string_2"
|
||||
default: "enum_string_1"
|
||||
```
|
||||
|
||||
## 布尔(Boolean)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- boolean_payload
|
||||
properties:
|
||||
boolean_payload:
|
||||
type: boolean
|
||||
default: true
|
||||
```
|
||||
|
||||
## 数字范围(Number or Integer)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- integer_payload
|
||||
properties:
|
||||
integer_payload:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
```
|
||||
|
||||
## 字符串长度(String)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- string_payload
|
||||
properties:
|
||||
string_payload:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
```
|
||||
|
||||
## 正则表达式(Regex)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- regex_payload
|
||||
properties:
|
||||
regex_payload:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
pattern: "^[a-zA-Z0-9_]+$"
|
||||
```
|
||||
|
||||
## 数组(Array)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- array_payload
|
||||
properties:
|
||||
array_payload:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
uniqueItems: true
|
||||
default: [1, 2, 3]
|
||||
```
|
||||
|
||||
## 多字段组合(Combined)验证
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- boolean_payload
|
||||
- array_payload
|
||||
- regex_payload
|
||||
properties:
|
||||
boolean_payload:
|
||||
type: boolean
|
||||
array_payload:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
uniqueItems: true
|
||||
default: [1, 2, 3]
|
||||
regex_payload:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
pattern: "^[a-zA-Z0-9_]+$"
|
||||
```
|
||||
|
||||
## 自定义拒绝信息
|
||||
```yaml
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- boolean_payload
|
||||
properties:
|
||||
boolean_payload:
|
||||
type: boolean
|
||||
rejected_code: 403
|
||||
rejected_msg: "请求被拒绝"
|
||||
```
|
||||
|
||||
# 本地调试
|
||||
|
||||
参考[使用 GO 语言开发 WASM 插件](https://higress.io/zh-cn/docs/user/wasm-go#%E4%B8%89%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95)
|
||||
1
plugins/wasm-go/extensions/request-validation/VERSION
Normal file
1
plugins/wasm-go/extensions/request-validation/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
18
plugins/wasm-go/extensions/request-validation/go.mod
Normal file
18
plugins/wasm-go/extensions/request-validation/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module github.com/alibaba/higress/plugins/wasm-go/extensions/request-validation
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v1.3.1
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
|
||||
github.com/magefile/mage v1.14.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
)
|
||||
22
plugins/wasm-go/extensions/request-validation/go.sum
Normal file
22
plugins/wasm-go/extensions/request-validation/go.sum
Normal file
@@ -0,0 +1,22 @@
|
||||
github.com/alibaba/higress/plugins/wasm-go v1.3.1 h1:d+t4W2NyqmqUz6DPZENflODfkLgdVlTfyso+nq0fSkg=
|
||||
github.com/alibaba/higress/plugins/wasm-go v1.3.1/go.mod h1:WZ/68vwe8qWhusa6C4/gMwUqas0jvHWSOa1bp8iK8F4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
199
plugins/wasm-go/extensions/request-validation/main.go
Normal file
199
plugins/wasm-go/extensions/request-validation/main.go
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2022 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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/santhosh-tekuri/jsonschema"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHeaderSchema = "header"
|
||||
defaultBodySchema = "body"
|
||||
defaultRejectedCode = 403
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"request-validation",
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
)
|
||||
}
|
||||
|
||||
// Config is the config for request validation.
|
||||
type Config struct {
|
||||
// compiler is the compiler for json schema.
|
||||
compiler *jsonschema.Compiler
|
||||
// rejectedCode is the code for rejected request.
|
||||
rejectedCode uint32
|
||||
// rejectedMsg is the message for rejected request.
|
||||
rejectedMsg string
|
||||
// draft is the draft version of json schema.
|
||||
draft *jsonschema.Draft
|
||||
// enableBodySchema is the flag for enable body schema.
|
||||
enableBodySchema bool
|
||||
// enableHeaderSchema is the flag for enable header schema.
|
||||
enableHeaderSchema bool
|
||||
}
|
||||
|
||||
func parseConfig(result gjson.Result, config *Config, log wrapper.Log) error {
|
||||
headerSchema := result.Get("header_schema").String()
|
||||
bodySchema := result.Get("body_schema").String()
|
||||
enableSwagger := result.Get("enable_swagger").Bool()
|
||||
enableOas3 := result.Get("enable_oas3").Bool()
|
||||
code := result.Get("rejected_code").Int()
|
||||
msg := result.Get("rejected_msg").String()
|
||||
|
||||
// set config default value
|
||||
config.enableBodySchema = false
|
||||
config.enableHeaderSchema = false
|
||||
|
||||
// check enable_swagger and enable_oas3
|
||||
if enableSwagger && enableOas3 {
|
||||
return fmt.Errorf("enable_swagger and enable_oas3 can not be true at the same time")
|
||||
}
|
||||
|
||||
// set draft version
|
||||
if enableSwagger {
|
||||
config.draft = jsonschema.Draft4
|
||||
}
|
||||
if enableOas3 {
|
||||
config.draft = jsonschema.Draft7
|
||||
}
|
||||
if !enableSwagger && !enableOas3 {
|
||||
config.draft = jsonschema.Draft7
|
||||
}
|
||||
|
||||
// create compiler
|
||||
compiler := jsonschema.NewCompiler()
|
||||
compiler.Draft = config.draft
|
||||
config.compiler = compiler
|
||||
|
||||
// add header schema to compiler
|
||||
if headerSchema != "" {
|
||||
err := config.compiler.AddResource(defaultHeaderSchema, strings.NewReader(headerSchema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.enableHeaderSchema = true
|
||||
}
|
||||
|
||||
// add body schema to compiler
|
||||
if bodySchema != "" {
|
||||
err := config.compiler.AddResource(defaultBodySchema, strings.NewReader(bodySchema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.enableBodySchema = true
|
||||
}
|
||||
|
||||
// check rejected_code is valid
|
||||
if code != 0 && code > 100 && code < 600 {
|
||||
config.rejectedCode = uint32(code)
|
||||
} else {
|
||||
config.rejectedCode = defaultRejectedCode
|
||||
}
|
||||
config.rejectedMsg = msg
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Log) types.Action {
|
||||
if !config.enableHeaderSchema {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// get headers
|
||||
headers, err := proxywasm.GetHttpRequestHeaders()
|
||||
if err != nil {
|
||||
log.Errorf("get request headers failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// covert to schema
|
||||
schema := make(map[string]interface{})
|
||||
for _, header := range headers {
|
||||
schema[header[0]] = header[1]
|
||||
}
|
||||
|
||||
// convert to json string
|
||||
schemaBytes, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
log.Errorf("marshal schema failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// validate
|
||||
document := strings.NewReader(string(schemaBytes))
|
||||
compile, err := config.compiler.Compile(defaultHeaderSchema)
|
||||
if err != nil {
|
||||
log.Errorf("compile schema failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
err = compile.Validate(document)
|
||||
if err != nil {
|
||||
log.Errorf("validate request headers failed: %v", err)
|
||||
proxywasm.SendHttpResponse(config.rejectedCode, nil, []byte(config.rejectedMsg), -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, config Config, body []byte, log wrapper.Log) types.Action {
|
||||
if !config.enableBodySchema {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// covert to schema
|
||||
schema := make(map[string]interface{})
|
||||
err := json.Unmarshal(body, &schema)
|
||||
if err != nil {
|
||||
log.Errorf("unmarshal body failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// convert to json string
|
||||
schemaBytes, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
log.Errorf("marshal schema failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// validate
|
||||
document := strings.NewReader(string(schemaBytes))
|
||||
compile, err := config.compiler.Compile(defaultBodySchema)
|
||||
if err != nil {
|
||||
log.Errorf("compile schema failed: %v", err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
err = compile.Validate(document)
|
||||
if err != nil {
|
||||
log.Errorf("validate request body failed: %v", err)
|
||||
proxywasm.SendHttpResponse(config.rejectedCode, nil, []byte(config.rejectedMsg), -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
412
test/e2e/conformance/tests/go-wasm-request-validation.go
Normal file
412
test/e2e/conformance/tests/go-wasm-request-validation.go
Normal file
@@ -0,0 +1,412 @@
|
||||
// Copyright (c) 2022 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 tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register(WasmPluginsRequestValidation)
|
||||
}
|
||||
|
||||
var WasmPluginsRequestValidation = suite.ConformanceTest{
|
||||
ShortName: "WasmPluginsRequestValidation",
|
||||
Description: "The Ingress in the higress-conformance-infra namespace test the request-validation wasmplugins.",
|
||||
Manifests: []string{"tests/go-wasm-request-validation.yaml"},
|
||||
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testCases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "request validation pass",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "header lack of require parameter",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body lack of require parameter",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body enum payload not in enum list",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_3",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body bool payload not bool type",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": "string",
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body integer payload not in range",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 70000,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body string payload length not in range",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "a",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body regex payload not match regex pattern",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc@123",
|
||||
"array_payload": [200, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body array payload not in array range",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [150, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body array payload not unique array items",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [302, 302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "body array payload length not in range",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
CompareTarget: http.CompareTargetResponse,
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Host: "foo.com",
|
||||
Path: "/foo",
|
||||
Headers: map[string]string{
|
||||
"enum_payload": "enum_string_1",
|
||||
},
|
||||
Body: []byte(`
|
||||
{
|
||||
"enum_payload": "enum_string_1",
|
||||
"bool_payload": true,
|
||||
"integer_payload": 100,
|
||||
"string_payload": "abc",
|
||||
"regex_payload": "abc123",
|
||||
"array_payload": [302]
|
||||
}
|
||||
`),
|
||||
ContentType: http.ContentTypeApplicationJson,
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 403,
|
||||
Body: []byte(`customize reject message`),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("WasmPlugins request-validation", func(t *testing.T) {
|
||||
for _, testcase := range testCases {
|
||||
t.Logf("Running test case: %s", testcase.Meta.TestCaseName)
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
97
test/e2e/conformance/tests/go-wasm-request-validation.yaml
Normal file
97
test/e2e/conformance/tests/go-wasm-request-validation.yaml
Normal file
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) 2022 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.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: httproute-app-root
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-validation
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfig:
|
||||
header_schema:
|
||||
type: object
|
||||
required:
|
||||
- enum_payload
|
||||
properties:
|
||||
enum_payload:
|
||||
type: string
|
||||
enum:
|
||||
- "enum_string_1"
|
||||
- "enum_string_2"
|
||||
default: "enum_string_1"
|
||||
body_schema:
|
||||
type: object
|
||||
required:
|
||||
- enum_payload
|
||||
- bool_payload
|
||||
- integer_payload
|
||||
- string_payload
|
||||
- regex_payload
|
||||
- array_payload
|
||||
properties:
|
||||
enum_payload:
|
||||
type: string
|
||||
enum:
|
||||
- "enum_string_1"
|
||||
- "enum_string_2"
|
||||
default: "enum_string_1"
|
||||
bool_payload:
|
||||
type: boolean
|
||||
default: true
|
||||
integer_payload:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
string_payload:
|
||||
type: string
|
||||
minLength: 2
|
||||
maxLength: 32
|
||||
regex_payload:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 32
|
||||
pattern: "^[a-zA-Z0-9]+$"
|
||||
array_payload:
|
||||
type: array
|
||||
minItems: 2
|
||||
items:
|
||||
type: integer
|
||||
minimum: 200
|
||||
maximum: 599
|
||||
uniqueItems: true
|
||||
default: [200, 302]
|
||||
rejected_code: 403
|
||||
rejected_msg: "customize reject message"
|
||||
enable_swagger: true
|
||||
enable_oas3: false
|
||||
url: file:///opt/plugins/wasm-go/extensions/request-validation/plugin.wasm
|
||||
Reference in New Issue
Block a user