Skip to content

Commit a617999

Browse files
authored
feature: new plugin request-validator (#1709)
1 parent a5fc25c commit a617999

File tree

6 files changed

+640
-1
lines changed

6 files changed

+640
-1
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
--
2+
-- Licensed to the Apache Software Foundation (ASF) under one or more
3+
-- contributor license agreements. See the NOTICE file distributed with
4+
-- this work for additional information regarding copyright ownership.
5+
-- The ASF licenses this file to You under the Apache License, Version 2.0
6+
-- (the "License"); you may not use this file except in compliance with
7+
-- the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing, software
12+
-- distributed under the License is distributed on an "AS IS" BASIS,
13+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
-- See the License for the specific language governing permissions and
15+
-- limitations under the License.
16+
--
17+
local core = require("apisix.core")
18+
local plugin_name = "request-validation"
19+
local ngx = ngx
20+
local io = io
21+
22+
local schema = {
23+
type = "object",
24+
properties = {
25+
body_schema = {type = "object"},
26+
header_schema = {type = "object"}
27+
},
28+
anyOf = {
29+
{required = {"body_schema"}},
30+
{required = {"header_schema"}}
31+
}
32+
}
33+
34+
35+
local _M = {
36+
version = 0.1,
37+
priority = 2800,
38+
type = 'validation',
39+
name = plugin_name,
40+
schema = schema,
41+
}
42+
43+
44+
function _M.check_schema(conf)
45+
return core.schema.check(schema, conf)
46+
end
47+
48+
49+
function _M.rewrite(conf)
50+
local headers = ngx.req.get_headers()
51+
52+
if conf.header_schema then
53+
local ok, err = core.schema.check(conf.header_schema, headers)
54+
if not ok then
55+
core.log.error("req schema validation failed", err)
56+
core.response.exit(400, err)
57+
end
58+
end
59+
60+
if conf.body_schema then
61+
ngx.req.read_body()
62+
local req_body, error
63+
local body = ngx.req.get_body_data()
64+
65+
if not body then
66+
local filename = ngx.req.get_body_file()
67+
if not filename then
68+
return core.response.exit(500)
69+
end
70+
local fd = io.open(filename, 'rb')
71+
if not fd then
72+
return core.response.exit(500)
73+
end
74+
body = fd:read('*a')
75+
end
76+
77+
if headers["content-type"] == "application/x-www-form-urlencoded" then
78+
req_body, error = ngx.decode_args(body)
79+
else -- JSON as default
80+
req_body, error = core.json.decode(body)
81+
end
82+
83+
if not req_body then
84+
core.log.error('failed to decode the req body', error)
85+
return core.response.exit(400, error)
86+
end
87+
88+
local ok, err = core.schema.check(conf.body_schema, req_body)
89+
if not ok then
90+
core.log.error("req schema validation failed", err)
91+
return core.response.exit(400, err)
92+
end
93+
end
94+
end
95+
96+
return _M

conf/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ plugins: # plugin list
174174
- echo
175175
- authz-keycloak
176176
- uri-blocker
177+
- request-validation
177178

178179
stream_plugins:
179180
- mqtt-proxy

doc/plugins/request-validation.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<!--
2+
#
3+
# Licensed to the Apache Software Foundation (ASF) under one or more
4+
# contributor license agreements. See the NOTICE file distributed with
5+
# this work for additional information regarding copyright ownership.
6+
# The ASF licenses this file to You under the Apache License, Version 2.0
7+
# (the "License"); you may not use this file except in compliance with
8+
# the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
-->
19+
20+
[Chinese](request-validation-cn.md)
21+
22+
# Summary
23+
- [**Name**](#name)
24+
- [**Attributes**](#attributes)
25+
- [**How To Enable**](#how-to-enable)
26+
- [**Test Plugin**](#test-plugin)
27+
- [**Disable Plugin**](#disable-plugin)
28+
- [**Examples**](#examples)
29+
30+
31+
## Name
32+
33+
`request-validation` plugin validates the requests before forwarding to an upstream service. The validation plugin uses
34+
json-schema to validate the schema. The plugin can be used to validate the headers and body data.
35+
36+
For more information on schema, refer to [JSON schema](https://github.com/api7/jsonschema) for more information.
37+
38+
## Attributes
39+
40+
|Name |Requirement |Description|
41+
|--------- |-------- |-----------|
42+
| header_schema |optional |schema for the header data|
43+
| body_schema |optional |schema for the body data|
44+
45+
## How To Enable
46+
47+
Create a route and enable the request-validation plugin on the route:
48+
49+
```shell
50+
curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
51+
{
52+
"uri": "/get",
53+
"plugins": {
54+
"request-validation": {
55+
"body_schema": {
56+
"type": "object",
57+
"required": ["required_payload"],
58+
"properties": {
59+
"required_payload": {"type": "string"},
60+
"boolean_payload": {"type": "boolean"}
61+
}
62+
}
63+
}
64+
},
65+
"upstream": {
66+
"type": "roundrobin",
67+
"nodes": {
68+
"127.0.0.1:8080": 1
69+
}
70+
}
71+
}
72+
```
73+
74+
## Test Plugin
75+
76+
```shell
77+
curl --header "Content-Type: application/json" \
78+
--request POST \
79+
--data '{"boolean-payload":true,"required_payload":"hello"}' \
80+
http://127.0.0.1:9080/get
81+
```
82+
83+
If the schema is violated the plugin will yield a `400` bad request.
84+
85+
## Disable Plugin
86+
87+
Remove the corresponding json configuration in the plugin configuration to disable the `request-validation`.
88+
APISIX plugins are hot-reloaded, therefore no need to restart APISIX.
89+
90+
```shell
91+
curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
92+
{
93+
"uri": "/get",
94+
"plugins": {
95+
},
96+
"upstream": {
97+
"type": "roundrobin",
98+
"nodes": {
99+
"127.0.0.1:8080": 1
100+
}
101+
}
102+
}
103+
```
104+
105+
106+
## Examples:
107+
108+
**Using ENUMS:**
109+
110+
```shell
111+
"body_schema": {
112+
"type": "object",
113+
"required": ["required_payload"],
114+
"properties": {
115+
"emum_payload": {
116+
"type": "string",
117+
enum: ["enum_string_1", "enum_string_2"]
118+
default = "enum_string_1"
119+
}
120+
}
121+
}
122+
```
123+
124+
125+
**JSON with multiple levels:**
126+
127+
```shell
128+
"body_schema": {
129+
"type": "object",
130+
"required": ["required_payload"],
131+
"properties": {
132+
"boolean_payload": {"type": "boolean"},
133+
"child_element_name": {
134+
"type": "object",
135+
"properties": {
136+
"http_statuses": {
137+
"type": "array",
138+
"minItems": 1,
139+
"items": {
140+
"type": "integer",
141+
"minimum": 200,
142+
"maximum": 599
143+
},
144+
"uniqueItems": true,
145+
"default": [200, 201, 202, 203]
146+
}
147+
}
148+
}
149+
}
150+
}
151+
```

t/admin/plugins.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ __DATA__
3030
--- request
3131
GET /apisix/admin/plugins/list
3232
--- response_body_like eval
33-
qr/\["fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","openid-connect","wolf-rbac","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin","skywalking","serverless-post-function"\]/
33+
qr/\["fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin","skywalking","serverless-post-function"\]/
3434
--- no_error_log
3535
[error]
3636

t/debug/debug-mode.t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ loaded plugin and sort by priority: 4010 name: batch-requests
6060
loaded plugin and sort by priority: 4000 name: cors
6161
loaded plugin and sort by priority: 3000 name: ip-restriction
6262
loaded plugin and sort by priority: 2900 name: uri-blocker
63+
loaded plugin and sort by priority: 2800 name: request-validation
6364
loaded plugin and sort by priority: 2599 name: openid-connect
6465
loaded plugin and sort by priority: 2555 name: wolf-rbac
6566
loaded plugin and sort by priority: 2520 name: basic-auth

0 commit comments

Comments
 (0)