mirror of
https://github.com/alibaba/higress.git
synced 2026-02-20 22:10:56 +08:00
200 lines
5.5 KiB
Go
200 lines
5.5 KiB
Go
// 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/higress-group/proxy-wasm-go-sdk/proxywasm"
|
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
|
"github.com/santhosh-tekuri/jsonschema"
|
|
"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.SendHttpResponseWithDetail(config.rejectedCode, "request-validation.invalid_headers", 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.SendHttpResponseWithDetail(config.rejectedCode, "request-validation.invalid_body", nil, []byte(config.rejectedMsg), -1)
|
|
return types.ActionPause
|
|
}
|
|
|
|
return types.ActionContinue
|
|
}
|