mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 04:37:31 +08:00
feat: add request-validation plugin (#700)
Signed-off-by: sjcsjc123 <1401189096@qq.com>
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user