You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2020/11/28 05:29:11 UTC

[apisix] branch master updated: fix: validate plugin configuration in the DP (#2856)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 1c211ff  fix: validate plugin configuration in the DP (#2856)
1c211ff is described below

commit 1c211ff7b1f8dd3cb98e74da4a714596d7327e47
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Sat Nov 28 13:29:00 2020 +0800

    fix: validate plugin configuration in the DP (#2856)
    
    Fix #2834
---
 apisix/admin/plugins.lua                         |  66 +------------
 apisix/core/config_etcd.lua                      |  28 ++++++
 apisix/core/config_yaml.lua                      |  24 ++++-
 apisix/http/router/radixtree_host_uri.lua        |   2 +
 apisix/http/router/radixtree_uri.lua             |   2 +
 apisix/http/service.lua                          |   2 +
 apisix/plugin.lua                                |  98 +++++++++++++++++++
 apisix/plugins/openid-connect.lua                |  13 ++-
 apisix/router.lua                                |   4 +-
 apisix/stream/router/ip_port.lua                 |   2 +
 rockspec/apisix-master-0.rockspec                |   2 +-
 t/admin/health-check.t                           |   2 +-
 t/config-center-yaml/{route.t => global-rule.t}  | 119 +++++++++++------------
 t/config-center-yaml/route-service.t             |  53 ++++++++++
 t/config-center-yaml/route.t                     | 104 +++++++++++++++++---
 t/config-center-yaml/{route.t => stream-route.t} | 103 ++++++++------------
 t/core/etcd-sync.t                               |  38 ++++++++
 t/plugin/api-breaker.t                           |   2 +-
 t/plugin/basic-auth.t                            |  54 ++++++++--
 t/plugin/hmac-auth.t                             |  36 +++++--
 t/plugin/openid-connect.t                        |   8 +-
 21 files changed, 536 insertions(+), 226 deletions(-)

diff --git a/apisix/admin/plugins.lua b/apisix/admin/plugins.lua
index 4a95d18..78211f0 100644
--- a/apisix/admin/plugins.lua
+++ b/apisix/admin/plugins.lua
@@ -16,12 +16,10 @@
 --
 local require   = require
 local core = require("apisix.core")
-local local_plugins = require("apisix.plugin").plugins_hash
-local stream_local_plugins = require("apisix.plugin").stream_plugins_hash
-local pairs     = pairs
+local check_schema = require("apisix.plugin").check_schema
+local stream_check_schema = require("apisix.plugin").stream_check_schema
 local ipairs    = ipairs
 local pcall     = pcall
-local type      = type
 local table_sort = table.sort
 local table_insert = table.insert
 local get_uri_args = ngx.req.get_uri_args
@@ -30,68 +28,12 @@ local _M = {}
 
 
 function _M.check_schema(plugins_conf, schema_type)
-    for name, plugin_conf in pairs(plugins_conf) do
-        core.log.info("check plugin scheme, name: ", name, ", configurations: ",
-                      core.json.delay_encode(plugin_conf, true))
-        if type(plugin_conf) ~= "table" then
-            return false, "invalid plugin conf " ..
-                core.json.encode(plugin_conf, true) ..
-                " for plugin [" .. name .. "]"
-        end
-
-        local plugin_obj = local_plugins[name]
-        if not plugin_obj then
-            return false, "unknown plugin [" .. name .. "]"
-        end
-
-        if plugin_obj.check_schema then
-            local disable = plugin_conf.disable
-            plugin_conf.disable = nil
-
-            local ok, err = plugin_obj.check_schema(plugin_conf, schema_type)
-            if not ok then
-                return false, "failed to check the configuration of plugin "
-                              .. name .. " err: " .. err
-            end
-
-            plugin_conf.disable = disable
-        end
-    end
-
-    return true
+    return check_schema(plugins_conf, schema_type, false)
 end
 
 
 function _M.stream_check_schema(plugins_conf, schema_type)
-    for name, plugin_conf in pairs(plugins_conf) do
-        core.log.info("check stream plugin scheme, name: ", name,
-                      ": ", core.json.delay_encode(plugin_conf, true))
-        if type(plugin_conf) ~= "table" then
-            return false, "invalid plugin conf " ..
-                core.json.encode(plugin_conf, true) ..
-                " for plugin [" .. name .. "]"
-        end
-
-        local plugin_obj = stream_local_plugins[name]
-        if not plugin_obj then
-            return false, "unknown plugin [" .. name .. "]"
-        end
-
-        if plugin_obj.check_schema then
-            local disable = plugin_conf.disable
-            plugin_conf.disable = nil
-
-            local ok, err = plugin_obj.check_schema(plugin_conf, schema_type)
-            if not ok then
-                return false, "failed to check the configuration of "
-                              .. "stream plugin [" .. name .. "]: " .. err
-            end
-
-            plugin_conf.disable = disable
-        end
-    end
-
-    return true
+    return stream_check_schema(plugins_conf, schema_type, false)
 end
 
 
diff --git a/apisix/core/config_etcd.lua b/apisix/core/config_etcd.lua
index 161eb7d..ee69b34 100644
--- a/apisix/core/config_etcd.lua
+++ b/apisix/core/config_etcd.lua
@@ -216,6 +216,14 @@ local function sync_data(self)
                 end
             end
 
+            if data_valid and self.checker then
+                data_valid, err = self.checker(item.value)
+                if not data_valid then
+                    log.error("failed to check item data of [", self.key,
+                              "] err:", err, " ,val: ", json.delay_encode(item.value))
+                end
+            end
+
             if data_valid then
                 changed = true
                 insert_tab(self.values, item)
@@ -256,6 +264,14 @@ local function sync_data(self)
                     end
                 end
 
+                if data_valid and self.checker then
+                    data_valid, err = self.checker(item.value)
+                    if not data_valid then
+                        log.error("failed to check item data of [", self.key,
+                                  "] err:", err, " ,val: ", json.delay_encode(item.value))
+                    end
+                end
+
                 if data_valid then
                     changed = true
                     insert_tab(self.values, item)
@@ -349,6 +365,16 @@ local function sync_data(self)
                 return false, "failed to check item data of ["
                                 .. self.key .. "] err:" .. err
             end
+
+            if self.checker then
+                local ok, err = self.checker(res.value)
+                if not ok then
+                    self:upgrade_version(res.modifiedIndex)
+
+                    return false, "failed to check item data of ["
+                                    .. self.key .. "] err:" .. err
+                end
+            end
         end
 
         self:upgrade_version(res.modifiedIndex)
@@ -538,6 +564,7 @@ function _M.new(key, opts)
     local filter_fun = opts and opts.filter
     local timeout = opts and opts.timeout
     local single_item = opts and opts.single_item
+    local checker = opts and opts.checker
 
     local obj = setmetatable({
         etcd_cli = nil,
@@ -545,6 +572,7 @@ function _M.new(key, opts)
         key = key and prefix .. key,
         automatic = automatic,
         item_schema = item_schema,
+        checker = checker,
         sync_times = 0,
         running = true,
         conf_version = 0,
diff --git a/apisix/core/config_yaml.lua b/apisix/core/config_yaml.lua
index 16bbeb8..733761f 100644
--- a/apisix/core/config_yaml.lua
+++ b/apisix/core/config_yaml.lua
@@ -157,6 +157,14 @@ local function sync_data(self)
                 log.error("failed to check item data of [", self.key,
                           "] err:", err, " ,val: ", json.delay_encode(item))
             end
+
+            if data_valid and self.checker then
+                data_valid, err = self.checker(item)
+                if not data_valid then
+                    log.error("failed to check item data of [", self.key,
+                              "] err:", err, " ,val: ", json.delay_encode(item))
+                end
+            end
         end
 
         if data_valid then
@@ -180,8 +188,8 @@ local function sync_data(self)
             if type(item) ~= "table" then
                 data_valid = false
                 log.error("invalid item data of [", self.key .. "/" .. id,
-                            "], val: ", json.delay_encode(item),
-                            ", it shoud be a object")
+                          "], val: ", json.delay_encode(item),
+                          ", it shoud be a object")
             end
 
             local key = item.id or "arr_" .. i
@@ -192,7 +200,15 @@ local function sync_data(self)
                 data_valid, err = check_schema(self.item_schema, item)
                 if not data_valid then
                     log.error("failed to check item data of [", self.key,
-                            "] err:", err, " ,val: ", json.delay_encode(item))
+                              "] err:", err, " ,val: ", json.delay_encode(item))
+                end
+            end
+
+            if data_valid and self.checker then
+                data_valid, err = self.checker(item)
+                if not data_valid then
+                    log.error("failed to check item data of [", self.key,
+                              "] err:", err, " ,val: ", json.delay_encode(item))
                 end
             end
 
@@ -287,6 +303,7 @@ function _M.new(key, opts)
     local item_schema = opts and opts.item_schema
     local filter_fun = opts and opts.filter
     local single_item = opts and opts.single_item
+    local checker = opts and opts.checker
 
     -- like /routes and /upstreams, remove first char `/`
     if key then
@@ -296,6 +313,7 @@ function _M.new(key, opts)
     local obj = setmetatable({
         automatic = automatic,
         item_schema = item_schema,
+        checker = checker,
         sync_times = 0,
         running = true,
         conf_version = 0,
diff --git a/apisix/http/router/radixtree_host_uri.lua b/apisix/http/router/radixtree_host_uri.lua
index 97bad34..582b72b 100644
--- a/apisix/http/router/radixtree_host_uri.lua
+++ b/apisix/http/router/radixtree_host_uri.lua
@@ -17,6 +17,7 @@
 local require = require
 local router = require("resty.radixtree")
 local core = require("apisix.core")
+local plugin_checker = require("apisix.plugin").plugin_checker
 local ipairs = ipairs
 local type = type
 local error = error
@@ -160,6 +161,7 @@ function _M.init_worker(filter)
     user_routes, err = core.config.new("/routes", {
             automatic = true,
             item_schema = core.schema.route,
+            checker = plugin_checker,
             filter = filter,
         })
     if not user_routes then
diff --git a/apisix/http/router/radixtree_uri.lua b/apisix/http/router/radixtree_uri.lua
index 812b84c..4c9c4e9 100644
--- a/apisix/http/router/radixtree_uri.lua
+++ b/apisix/http/router/radixtree_uri.lua
@@ -17,6 +17,7 @@
 local require = require
 local router = require("resty.radixtree")
 local core = require("apisix.core")
+local plugin_checker = require("apisix.plugin").plugin_checker
 local ipairs = ipairs
 local type = type
 local error = error
@@ -116,6 +117,7 @@ function _M.init_worker(filter)
     user_routes, err = core.config.new("/routes", {
             automatic = true,
             item_schema = core.schema.route,
+            checker = plugin_checker,
             filter = filter,
         })
     if not user_routes then
diff --git a/apisix/http/service.lua b/apisix/http/service.lua
index 161d82f..a23924d 100644
--- a/apisix/http/service.lua
+++ b/apisix/http/service.lua
@@ -15,6 +15,7 @@
 -- limitations under the License.
 --
 local core   = require("apisix.core")
+local plugin_checker = require("apisix.plugin").plugin_checker
 local ipairs = ipairs
 local services
 local error = error
@@ -87,6 +88,7 @@ function _M.init_worker()
     services, err = core.config.new("/services", {
         automatic = true,
         item_schema = core.schema.service,
+        checker = plugin_checker,
         filter = filter,
     })
     if not services then
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index a319fbc..c681265 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -498,4 +498,102 @@ function _M.get(name)
 end
 
 
+local function check_schema(plugins_conf, schema_type, skip_disabled_plugin)
+    for name, plugin_conf in pairs(plugins_conf) do
+        core.log.info("check plugin schema, name: ", name, ", configurations: ",
+                      core.json.delay_encode(plugin_conf, true))
+        if type(plugin_conf) ~= "table" then
+            return false, "invalid plugin conf " ..
+                core.json.encode(plugin_conf, true) ..
+                " for plugin [" .. name .. "]"
+        end
+
+        local plugin_obj = local_plugins_hash[name]
+        if not plugin_obj then
+            if skip_disabled_plugin then
+                goto CONTINUE
+            else
+                return false, "unknown plugin [" .. name .. "]"
+            end
+        end
+
+        if plugin_obj.check_schema then
+            local disable = plugin_conf.disable
+            plugin_conf.disable = nil
+
+            local ok, err = plugin_obj.check_schema(plugin_conf, schema_type)
+            if not ok then
+                return false, "failed to check the configuration of plugin "
+                              .. name .. " err: " .. err
+            end
+
+            plugin_conf.disable = disable
+        end
+
+        ::CONTINUE::
+    end
+
+    return true
+end
+_M.check_schema = check_schema
+
+
+local function stream_check_schema(plugins_conf, schema_type, skip_disabled_plugin)
+    for name, plugin_conf in pairs(plugins_conf) do
+        core.log.info("check stream plugin schema, name: ", name,
+                      ": ", core.json.delay_encode(plugin_conf, true))
+        if type(plugin_conf) ~= "table" then
+            return false, "invalid plugin conf " ..
+                core.json.encode(plugin_conf, true) ..
+                " for plugin [" .. name .. "]"
+        end
+
+        local plugin_obj = stream_local_plugins_hash[name]
+        if not plugin_obj then
+            if skip_disabled_plugin then
+                goto CONTINUE
+            else
+                return false, "unknown plugin [" .. name .. "]"
+            end
+        end
+
+        if plugin_obj.check_schema then
+            local disable = plugin_conf.disable
+            plugin_conf.disable = nil
+
+            local ok, err = plugin_obj.check_schema(plugin_conf, schema_type)
+            if not ok then
+                return false, "failed to check the configuration of "
+                              .. "stream plugin [" .. name .. "]: " .. err
+            end
+
+            plugin_conf.disable = disable
+        end
+
+        ::CONTINUE::
+    end
+
+    return true
+end
+_M.stream_check_schema = stream_check_schema
+
+
+function _M.plugin_checker(item)
+    if item.plugins then
+        return check_schema(item.plugins, nil, true)
+    end
+
+    return true
+end
+
+
+function _M.stream_plugin_checker(item)
+    if item.plugins then
+        return stream_check_schema(item.plugins, nil, true)
+    end
+
+    return true
+end
+
+
 return _M
diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua
index 29aca4c..1dca419 100644
--- a/apisix/plugins/openid-connect.lua
+++ b/apisix/plugins/openid-connect.lua
@@ -54,6 +54,11 @@ local _M = {
 }
 
 function _M.check_schema(conf)
+    if conf.ssl_verify == "no" then
+        -- we used to set 'ssl_verify' to "no"
+        conf.ssl_verify = false
+    end
+
     local ok, err = core.schema.check(schema, conf)
     if not ok then
         return false, err
@@ -63,7 +68,9 @@ function _M.check_schema(conf)
         conf.scope = "openid"
     end
     if not conf.ssl_verify then
-        conf.ssl_verify = "no"
+        -- we need to use a boolean default value here
+        -- so that the schema can pass check in the DP
+        conf.ssl_verify = false
     end
     if not conf.timeout then
         conf.timeout = 3
@@ -144,6 +151,10 @@ function _M.access(plugin_conf, ctx)
     if not conf.redirect_uri then
         conf.redirect_uri = ctx.var.request_uri
     end
+    if not conf.ssl_verify then
+        -- openidc use "no" to disable ssl verification
+        conf.ssl_verify = "no"
+    end
 
     local response, err
     if conf.introspection_endpoint or conf.public_key then
diff --git a/apisix/router.lua b/apisix/router.lua
index 87c4662..b76b16d 100644
--- a/apisix/router.lua
+++ b/apisix/router.lua
@@ -16,6 +16,7 @@
 --
 local require = require
 local core    = require("apisix.core")
+local plugin_checker = require("apisix.plugin").plugin_checker
 local error   = error
 local pairs   = pairs
 local ipairs  = ipairs
@@ -88,7 +89,8 @@ function _M.http_init_worker()
 
     local global_rules, err = core.config.new("/global_rules", {
             automatic = true,
-            item_schema = core.schema.global_rule
+            item_schema = core.schema.global_rule,
+            checker = plugin_checker,
         })
     if not global_rules then
         error("failed to create etcd instance for fetching /global_rules : "
diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua
index 23f020d..85e18a5 100644
--- a/apisix/stream/router/ip_port.lua
+++ b/apisix/stream/router/ip_port.lua
@@ -15,6 +15,7 @@
 -- limitations under the License.
 --
 local core      = require("apisix.core")
+local plugin_checker = require("apisix.plugin").stream_plugin_checker
 local ipairs    = ipairs
 local error     = error
 local ngx_exit  = ngx.exit
@@ -84,6 +85,7 @@ function _M.stream_init_worker(filter)
     user_routes, err = core.config.new("/stream_routes", {
             automatic = true,
             item_schema = core.schema.stream_route,
+            checker = plugin_checker,
             filter = filter,
         })
 
diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec
index aac126a..67b4207 100644
--- a/rockspec/apisix-master-0.rockspec
+++ b/rockspec/apisix-master-0.rockspec
@@ -47,7 +47,7 @@ dependencies = {
     "luafilesystem = 1.7.0-2",
     "lua-tinyyaml = 1.0",
     "lua-resty-prometheus = 1.1",
-    "jsonschema = 0.8",
+    "jsonschema = 0.9.2",
     "lua-resty-ipmatcher = 0.6",
     "lua-resty-kafka = 0.07",
     "lua-resty-logger-socket = 2.0-0",
diff --git a/t/admin/health-check.t b/t/admin/health-check.t
index 698d69d..54905bc 100644
--- a/t/admin/health-check.t
+++ b/t/admin/health-check.t
@@ -288,7 +288,7 @@ GET /t
 GET /t
 --- error_code: 400
 --- response_body
-{"error_msg":"invalid configuration: property \"upstream\" validation failed: property \"checks\" validation failed: property \"active\" validation failed: property \"healthy\" validation failed: property \"http_statuses\" validation failed: expected unique items but items 2 and 1 are equal"}
+{"error_msg":"invalid configuration: property \"upstream\" validation failed: property \"checks\" validation failed: property \"active\" validation failed: property \"healthy\" validation failed: property \"http_statuses\" validation failed: expected unique items but items 1 and 2 are equal"}
 --- no_error_log
 [error]
 
diff --git a/t/config-center-yaml/route.t b/t/config-center-yaml/global-rule.t
similarity index 52%
copy from t/config-center-yaml/route.t
copy to t/config-center-yaml/global-rule.t
index 81a37d1..00813fa 100644
--- a/t/config-center-yaml/route.t
+++ b/t/config-center-yaml/global-rule.t
@@ -21,103 +21,94 @@ log_level('info');
 no_root_location();
 no_shuffle();
 
-our $yaml_config = <<_EOC_;
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = $block->yaml_config // <<_EOC_;
 apisix:
     node_listen: 1984
     config_center: yaml
     enable_admin: false
 _EOC_
 
+    $block->set_value("yaml_config", $yaml_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /hello");
+    }
+
+    if (!$block->error_log && !$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
 run_tests();
 
 __DATA__
 
 === TEST 1: sanity
---- yaml_config eval: $::yaml_config
 --- apisix_yaml
 routes:
-  -
-    id: 1
-    uri: /hello
-    upstream:
-        nodes:
-            "127.0.0.1:1980": 1
-        type: roundrobin
+    -
+        id: 1
+        uri: /hello
+        upstream:
+            nodes:
+                "127.0.0.1:1980": 1
+            type: roundrobin
+global_rules:
+    -
+        id: 1
+        plugins:
+            response-rewrite:
+                body: "hello\n"
 #END
---- request
-GET /hello
 --- response_body
-hello world
---- error_log
-use config_center: yaml
---- no_error_log
-[error]
+hello
 
 
 
-=== TEST 2: route:uri + host (missing host, not hit)
---- yaml_config eval: $::yaml_config
+=== TEST 2: global rule with bad plugin
 --- apisix_yaml
 routes:
-  -
-    id: 1
-    uri: /hello
-    host: foo.com
-    upstream:
-        nodes:
-            "127.0.0.1:1980": 1
-        type: roundrobin
+    -
+        id: 1
+        uri: /hello
+        upstream:
+            nodes:
+                "127.0.0.1:1980": 1
+            type: roundrobin
+global_rules:
+    -
+        id: 1
+        plugins:
+            response-rewrite:
+                body: 4
 #END
---- request
-GET /hello
---- error_code: 404
+--- response_body
+hello world
 --- error_log
-use config_center: yaml
---- no_error_log
-[error]
+property "body" validation failed
 
 
 
-=== TEST 3: route:uri + host
---- yaml_config eval: $::yaml_config
+=== TEST 3: fix global rule with default value
 --- apisix_yaml
 routes:
   -
     id: 1
     uri: /hello
-    host: foo.com
     upstream:
         nodes:
             "127.0.0.1:1980": 1
         type: roundrobin
-#END
---- more_headers
-host: foo.com
---- request
-GET /hello
---- response_body
-hello world
---- no_error_log
-[error]
-
 
-
-=== TEST 4: stream route
---- yaml_config eval: $::yaml_config
---- apisix_yaml
-stream_routes:
-  - server_addr: 127.0.0.1
-    server_port: 1985
-    id: 1
-    upstream:
-      nodes:
-        "127.0.0.1:1995": 1
-      type: roundrobin
+global_rules:
+    -
+        id: 1
+        plugins:
+            uri-blocker:
+                block_rules:
+                    - /h*
 #END
---- stream_enable
---- stream_request eval
-mmm
---- stream_response
-hello world
---- no_error_log
-[error]
+--- error_code: 403
diff --git a/t/config-center-yaml/route-service.t b/t/config-center-yaml/route-service.t
index 8876022..c83af6f 100644
--- a/t/config-center-yaml/route-service.t
+++ b/t/config-center-yaml/route-service.t
@@ -252,3 +252,56 @@ GET /hello
 hello
 --- no_error_log
 [error]
+
+
+
+=== TEST 8: service with bad plugin
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+    -
+        id: 1
+        uri: /hello
+        service_id: 1
+services:
+    -
+        id: 1
+        plugins:
+            proxy-rewrite:
+                uri: 1
+        upstream:
+            nodes:
+                "127.0.0.1:1980": 1
+            type: roundrobin
+#END
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 9: fix service with default value
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+    -
+        id: 1
+        uri: /hello
+        service_id: 1
+services:
+    -
+        id: 1
+        plugins:
+            uri-blocker:
+                block_rules:
+                    - /h*
+        upstream:
+            nodes:
+                "127.0.0.1:1980": 1
+            type: roundrobin
+#END
+--- request
+GET /hello
+--- error_code: 403
diff --git a/t/config-center-yaml/route.t b/t/config-center-yaml/route.t
index 81a37d1..ba6c137 100644
--- a/t/config-center-yaml/route.t
+++ b/t/config-center-yaml/route.t
@@ -102,22 +102,104 @@ hello world
 
 
 
-=== TEST 4: stream route
+=== TEST 4: route with bad plugin
 --- yaml_config eval: $::yaml_config
 --- apisix_yaml
-stream_routes:
-  - server_addr: 127.0.0.1
-    server_port: 1985
+routes:
+  -
+    id: 1
+    uri: /hello
+    plugins:
+        proxy-rewrite:
+            uri: 1
+    upstream:
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
+#END
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 5: ignore unknown plugin
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+  -
     id: 1
+    uri: /hello
+    plugins:
+        x-rewrite:
+            uri: 1
     upstream:
-      nodes:
-        "127.0.0.1:1995": 1
-      type: roundrobin
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
 #END
---- stream_enable
---- stream_request eval
-mmm
---- stream_response
+--- request
+GET /hello
+--- response_body
 hello world
 --- no_error_log
 [error]
+
+
+
+=== TEST 6: route with bad plugin, radixtree_host_uri
+--- yaml_config
+apisix:
+    node_listen: 1984
+    config_center: yaml
+    enable_admin: false
+    router:
+        http: "radixtree_host_uri"
+--- apisix_yaml
+routes:
+  -
+    id: 1
+    uri: /hello
+    plugins:
+        proxy-rewrite:
+            uri: 1
+    upstream:
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
+#END
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 7: fix route with default value
+--- yaml_config
+apisix:
+    node_listen: 1984
+    config_center: yaml
+    enable_admin: false
+    router:
+        http: "radixtree_host_uri"
+--- apisix_yaml
+routes:
+  -
+    id: 1
+    uri: /hello
+    plugins:
+        uri-blocker:
+            block_rules:
+                - /h*
+    upstream:
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
+#END
+--- request
+GET /hello
+--- error_code: 403
diff --git a/t/config-center-yaml/route.t b/t/config-center-yaml/stream-route.t
similarity index 55%
copy from t/config-center-yaml/route.t
copy to t/config-center-yaml/stream-route.t
index 81a37d1..2046797 100644
--- a/t/config-center-yaml/route.t
+++ b/t/config-center-yaml/stream-route.t
@@ -21,103 +21,82 @@ log_level('info');
 no_root_location();
 no_shuffle();
 
-our $yaml_config = <<_EOC_;
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = $block->yaml_config // <<_EOC_;
 apisix:
     node_listen: 1984
     config_center: yaml
     enable_admin: false
 _EOC_
 
+    $block->set_value("yaml_config", $yaml_config);
+
+    $block->set_value("stream_enable", 1);
+
+    if (!$block->stream_request) {
+        $block->set_value("stream_request", "mmm");
+    }
+
+    if (!$block->error_log && !$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
 run_tests();
 
 __DATA__
 
+
 === TEST 1: sanity
---- yaml_config eval: $::yaml_config
 --- apisix_yaml
-routes:
-  -
+stream_routes:
+  - server_addr: 127.0.0.1
+    server_port: 1985
     id: 1
-    uri: /hello
     upstream:
-        nodes:
-            "127.0.0.1:1980": 1
-        type: roundrobin
+      nodes:
+        "127.0.0.1:1995": 1
+      type: roundrobin
 #END
---- request
-GET /hello
---- response_body
+--- stream_response
 hello world
---- error_log
-use config_center: yaml
---- no_error_log
-[error]
 
 
 
-=== TEST 2: route:uri + host (missing host, not hit)
---- yaml_config eval: $::yaml_config
+=== TEST 2: rule with bad plugin
 --- apisix_yaml
-routes:
-  -
-    id: 1
-    uri: /hello
-    host: foo.com
-    upstream:
-        nodes:
-            "127.0.0.1:1980": 1
-        type: roundrobin
-#END
---- request
-GET /hello
---- error_code: 404
---- error_log
-use config_center: yaml
---- no_error_log
-[error]
-
-
-
-=== TEST 3: route:uri + host
---- yaml_config eval: $::yaml_config
---- apisix_yaml
-routes:
-  -
+stream_routes:
+  - server_addr: 127.0.0.1
+    server_port: 1985
     id: 1
-    uri: /hello
-    host: foo.com
+    plugins:
+        mqtt-proxy:
+            uri: 1
     upstream:
-        nodes:
-            "127.0.0.1:1980": 1
-        type: roundrobin
+      nodes:
+        "127.0.0.1:1995": 1
+      type: roundrobin
 #END
---- more_headers
-host: foo.com
---- request
-GET /hello
---- response_body
-hello world
---- no_error_log
-[error]
+--- error_log eval
+qr/property "\w+" is required/
 
 
 
-=== TEST 4: stream route
---- yaml_config eval: $::yaml_config
+=== TEST 3: ignore unknown plugin
 --- apisix_yaml
 stream_routes:
   - server_addr: 127.0.0.1
     server_port: 1985
     id: 1
+    plugins:
+        x-rewrite:
+            uri: 1
     upstream:
       nodes:
         "127.0.0.1:1995": 1
       type: roundrobin
 #END
---- stream_enable
---- stream_request eval
-mmm
 --- stream_response
 hello world
---- no_error_log
-[error]
diff --git a/t/core/etcd-sync.t b/t/core/etcd-sync.t
index 65a8bca..af4260e 100644
--- a/t/core/etcd-sync.t
+++ b/t/core/etcd-sync.t
@@ -154,3 +154,41 @@ GET /t
 prev_index not update
 --- no_error_log
 [error]
+
+
+
+=== TEST 4: bad plugin configuration (validated via incremental sync)
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+
+            assert(core.etcd.set("/global_rules/etcdsync",
+                {id = 1, plugins = { ["proxy-rewrite"] = { uri =  1 }}}
+            ))
+            -- wait for sync
+            ngx.sleep(0.6)
+        }
+    }
+--- request
+GET /t
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 5: bad plugin configuration (validated via full sync)
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            -- wait for full sync finish
+            ngx.sleep(0.6)
+
+            assert(core.etcd.delete("/global_rules/etcdsync"))
+        }
+    }
+--- request
+GET /t
+--- error_log
+property "uri" validation failed
diff --git a/t/plugin/api-breaker.t b/t/plugin/api-breaker.t
index 9b5ff4e..c9142cf 100644
--- a/t/plugin/api-breaker.t
+++ b/t/plugin/api-breaker.t
@@ -309,7 +309,7 @@ GET /t
 GET /t
 --- error_code: 400
 --- response_body
-{"error_msg":"failed to check the configuration of plugin api-breaker err: property \"healthy\" validation failed: property \"http_statuses\" validation failed: expected unique items but items 2 and 1 are equal"}
+{"error_msg":"failed to check the configuration of plugin api-breaker err: property \"healthy\" validation failed: property \"http_statuses\" validation failed: expected unique items but items 1 and 2 are equal"}
 --- no_error_log
 [error]
 
diff --git a/t/plugin/basic-auth.t b/t/plugin/basic-auth.t
index ebdf781..50ff775 100644
--- a/t/plugin/basic-auth.t
+++ b/t/plugin/basic-auth.t
@@ -291,29 +291,65 @@ GET /t
 
 
 === TEST 12: get the default schema
---- request
-GET /apisix/admin/schema/plugins/basic-auth
---- response_body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/schema/plugins/basic-auth',
+                ngx.HTTP_GET,
+                nil,
+                [[
 {"properties":{"disable":{"type":"boolean"}},"title":"work with route or service object","additionalProperties":false,"type":"object"}
+                ]]
+                )
+            ngx.status = code
+        }
+    }
+--- request
+GET /t
 --- no_error_log
 [error]
 
 
 
 === TEST 13: get the schema by schema_type
---- request
-GET /apisix/admin/schema/plugins/basic-auth?schema_type=consumer
---- response_body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/schema/plugins/basic-auth?schema_type=consumer',
+                ngx.HTTP_GET,
+                nil,
+                [[
 {"title":"work with consumer object","additionalProperties":false,"required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
+                ]]
+                )
+            ngx.status = code
+        }
+    }
+--- request
+GET /t
 --- no_error_log
 [error]
 
 
 
 === TEST 14: get the schema by error schema_type
---- request
-GET /apisix/admin/schema/plugins/basic-auth?schema_type=consumer123123
---- response_body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/schema/plugins/basic-auth?schema_type=consumer123123',
+                ngx.HTTP_GET,
+                nil,
+                [[
 {"properties":{"disable":{"type":"boolean"}},"title":"work with route or service object","additionalProperties":false,"type":"object"}
+                ]]
+                )
+            ngx.status = code
+        }
+    }
+--- request
+GET /t
 --- no_error_log
 [error]
diff --git a/t/plugin/hmac-auth.t b/t/plugin/hmac-auth.t
index e6fe620..a8c77e3 100644
--- a/t/plugin/hmac-auth.t
+++ b/t/plugin/hmac-auth.t
@@ -1259,10 +1259,22 @@ x-real-ip: 127.0.0.1
 
 
 === TEST 32: get the default schema
---- request
-GET /apisix/admin/schema/plugins/hmac-auth
---- response_body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/schema/plugins/hmac-auth',
+                ngx.HTTP_GET,
+                nil,
+                [[
 {"properties":{"disable":{"type":"boolean"}},"title":"work with route or service object","additionalProperties":false,"type":"object"}
+                ]]
+                )
+            ngx.status = code
+        }
+    }
+--- request
+GET /t
 --- no_error_log
 [error]
 
@@ -1279,10 +1291,22 @@ GET /apisix/admin/schema/plugins/hmac-auth?schema_type=consumer
 
 
 === TEST 34: get the schema by error schema_type
---- request
-GET /apisix/admin/schema/plugins/hmac-auth?schema_type=consumer123123
---- response_body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/schema/plugins/hmac-auth?schema_type=consumer123123',
+                ngx.HTTP_GET,
+                nil,
+                [[
 {"properties":{"disable":{"type":"boolean"}},"title":"work with route or service object","additionalProperties":false,"type":"object"}
+                ]]
+                )
+            ngx.status = code
+        }
+    }
+--- request
+GET /t
 --- no_error_log
 [error]
 
diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t
index bc15ca7..d2d251b 100644
--- a/t/plugin/openid-connect.t
+++ b/t/plugin/openid-connect.t
@@ -127,7 +127,7 @@ done
                                     "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
                                     "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration",
                                     "redirect_uri": "https://iresty.com",
-                                    "ssl_verify": "no",
+                                    "ssl_verify": false,
                                     "timeout": 10000,
                                     "scope": "apisix"
                                 }
@@ -228,7 +228,7 @@ true
                                     "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
                                     "discovery": "https://samples.auth0.com/.well-known/openid-configuration",
                                     "redirect_uri": "https://iresty.com",
-                                    "ssl_verify": "no",
+                                    "ssl_verify": false,
                                     "timeout": 10000,
                                     "bearer_only": true,
                                     "scope": "apisix"
@@ -318,7 +318,7 @@ hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==
                                     "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
                                     "discovery": "https://samples.auth0.com/.well-known/openid-configuration",
                                     "redirect_uri": "https://iresty.com",
-                                    "ssl_verify": "no",
+                                    "ssl_verify": false,
                                     "timeout": 10000,
                                     "bearer_only": true,
                                     "scope": "apisix",
@@ -460,7 +460,7 @@ jwt signature verification failed
                                     "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
                                     "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration",
                                     "redirect_uri": "http://localhost:3000",
-                                    "ssl_verify": "no",
+                                    "ssl_verify": false,
                                     "timeout": 10000,
                                     "bearer_only": true,
                                     "realm": "University",