diff --git a/apisix/core/config_etcd.lua b/apisix/core/config_etcd.lua index fbb6df8dbf5d..ed46e77f8885 100644 --- a/apisix/core/config_etcd.lua +++ b/apisix/core/config_etcd.lua @@ -25,6 +25,7 @@ local exiting = ngx.worker.exiting local insert_tab = table.insert local type = type local ipairs = ipairs +local pairs = pairs local setmetatable = setmetatable local ngx_sleep = ngx.sleep local ngx_timer_at = ngx.timer.at @@ -49,6 +50,16 @@ local mt = { end } +local disable_schema = { + type = "object", + properties = { + disable = {type = "boolean", enum={true}} + }, + required = {"disable"} +} +local local_plugins +local stream_local_plugins + local function readdir(etcd_cli, key) if not etcd_cli then return nil, nil, "not inited" @@ -166,6 +177,39 @@ local function sync_data(self) ", it shoud be a object") end + -- check plugins schema + local plugins_conf = item.plugins + if plugins_conf then + for name, plugin_conf in pairs(plugins_conf) do + local plugin_obj = local_plugins[name] + if plugin_obj then + log.info("check plugin scheme, name: ", name, ", configurations: ", + json.delay_encode(plugin_conf, true)) + else + plugin_obj = stream_local_plugins[name] + if plugin_obj then + log.info("check stream plugin scheme, name: ", name, + ": ", json.delay_encode(plugin_conf, true)) + else + log.error("unknown plugin [" .. name .. "]") + return false + end + end + + if plugin_obj.check_schema then + local ok = check_schema(disable_schema, plugin_conf) + if not ok then + local ok, err = plugin_obj.check_schema(plugin_conf) + if not ok then + log.error("failed to check the configuration of plugin " + .. name .. " err: " .. err) + return false + end + end + end + end + end + if data_valid and self.item_schema then data_valid, err = check_schema(self.item_schema, item.value) if not data_valid then @@ -466,5 +510,12 @@ function _M.server_version(self) return read_etcd_version(self.etcd_cli) end +function _M.init_plugins(plugins, is_stream) + if is_stream then + stream_local_plugins = plugins + else + local_plugins = plugins + end +end return _M diff --git a/apisix/core/config_yaml.lua b/apisix/core/config_yaml.lua index bb1cd250af2a..c5d6fbe766e0 100644 --- a/apisix/core/config_yaml.lua +++ b/apisix/core/config_yaml.lua @@ -27,6 +27,7 @@ local exiting = ngx.worker.exiting local insert_tab = table.insert local type = type local ipairs = ipairs +local pairs = pairs local setmetatable = setmetatable local ngx_sleep = ngx.sleep local ngx_timer_at = ngx.timer.at @@ -55,9 +56,18 @@ local mt = { end } +local disable_schema = { + type = "object", + properties = { + disable = {type = "boolean", enum={true}} + }, + required = {"disable"} +} +local local_plugins +local stream_local_plugins - local apisix_yaml - local apisix_yaml_ctime +local apisix_yaml +local apisix_yaml_ctime local function read_apisix_yaml(premature, pre_mtime) if premature then return @@ -162,6 +172,39 @@ local function sync_data(self) ", it shoud be a object") end + -- check plugins schema + local plugins_conf = item.plugins + if plugins_conf then + for name, plugin_conf in pairs(plugins_conf) do + local plugin_obj = local_plugins[name] + if plugin_obj then + log.info("check plugin scheme, name: ", name, ", configurations: ", + json.delay_encode(plugin_conf, true)) + else + plugin_obj = stream_local_plugins[name] + if plugin_obj then + log.info("check stream plugin scheme, name: ", name, + ": ", json.delay_encode(plugin_conf, true)) + else + log.error("unknown plugin [" .. name .. "]") + return false + end + end + + if plugin_obj.check_schema then + local ok = check_schema(disable_schema, plugin_conf) + if not ok then + local ok, err = plugin_obj.check_schema(plugin_conf) + if not ok then + log.error("failed to check the configuration of plugin " + .. name .. " err: " .. err) + return false + end + end + end + end + end + local key = item.id or "arr_" .. i local conf_item = {value = item, modifiedIndex = apisix_yaml_ctime, key = "/" .. self.key .. "/" .. key} @@ -324,5 +367,12 @@ function _M.init_worker() ngx.timer.every(1, read_apisix_yaml) end +function _M.init_plugins(plugins, is_stream) + if is_stream then + stream_local_plugins = plugins + else + local_plugins = plugins + end +end return _M diff --git a/apisix/plugin.lua b/apisix/plugin.lua index 2cf5296a14ce..3db8a3df7982 100644 --- a/apisix/plugin.lua +++ b/apisix/plugin.lua @@ -124,6 +124,7 @@ local function load() _M.load_times = _M.load_times + 1 core.log.info("load plugin times: ", _M.load_times) + core.config.init_plugins(local_plugins_hash, false) return true end @@ -165,6 +166,7 @@ local function load_stream() core.log.info("stream plugins: ", core.json.delay_encode(stream_local_plugins, true)) core.log.info("load stream plugin times: ", _M.stream_load_times) + core.config.init_plugins(stream_local_plugins_hash, true) return true end diff --git a/t/config-center/etcd/route-plugin.t b/t/config-center/etcd/route-plugin.t new file mode 100644 index 000000000000..c14db6681719 --- /dev/null +++ b/t/config-center/etcd/route-plugin.t @@ -0,0 +1,146 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +BEGIN { + $ENV{"ETCD_ENABLE_AUTH"} = "true" +} + +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_shuffle(); +no_root_location(); +log_level("info"); + +# Authentication is enabled at etcd and credentials are set +system('etcdctl --endpoints="http://127.0.0.1:2379" -u root:5tHkHhYkjr6cQY user add root:5tHkHhYkjr6cQY'); +system('etcdctl --endpoints="http://127.0.0.1:2379" -u root:5tHkHhYkjr6cQY auth enable'); +system('etcdctl --endpoints="http://127.0.0.1:2379" -u root:5tHkHhYkjr6cQY role revoke --path "/*" -rw guest'); + +run_tests; + +# Authentication is disabled at etcd & guest access is granted +system('etcdctl --endpoints="http://127.0.0.1:2379" -u root:5tHkHhYkjr6cQY auth disable'); +system('etcdctl --endpoints="http://127.0.0.1:2379" -u root:5tHkHhYkjr6cQY role grant --path "/*" -rw guest'); + +__DATA__ + +=== TEST 1: set route with plugin +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local conf = { + ["uri"] = "/hello", + ["plugins"] = { + ["proxy-rewrite"] = { + ["uri"] = "/uri/plugin_proxy_rewrite", + ["headers"] = { + ["X-Api-Version"] = "v2" + } + } + }, + ["upstream"] = { + ["nodes"] = { + ["127.0.0.1:1980"] = 1 + }, + ["type"] = "roundrobin" + } + } + local res, err = core.etcd.push("/routes", conf) + if not res then + core.log.error("failed to post route[/routes] to etcd") + ngx.exit(code) + end + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 2: route with plugin +--- request +GET /hello +--- more_headers +X-Api-Version:v1 +--- response_body +uri: /uri/plugin_proxy_rewrite +host: localhost +x-api-version: v2 +x-real-ip: 127.0.0.1 +--- no_error_log +[error] + + + +=== TEST 3: set route with invalid plugin +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local sub_str = string.sub + local res, err = core.etcd.get("/routes") + if not res then + core.log.error("failed to get route[/routes] from etcd: ", err) + end + local key = sub_str(res.body.node.nodes[1].key, 8) + local conf = { + ["uri"] = "/hello", + ["plugins"] = { + ["proxy-rewrite"] = { + ["uri"] = "/uri/plugin_proxy_rewrite", + ["headers"] = { + [""] = "" + } + } + }, + ["upstream"] = { + ["nodes"] = { + ["127.0.0.1:1980"] = 1 + }, + ["type"] = "roundrobin" + } + } + local res, err = core.etcd.set(key, conf) + if not res then + core.log.error("failed to put route[/routes] to etcd") + ngx.exit(code) + end + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 4: route with invalid plugin +--- request +GET /hello +--- error_code: 404 +--- error_log +failed to check the configuration of plugin proxy-rewrite err: invalid type as header value, context: ngx.timer diff --git a/t/config-center/yaml/route-plugin.t b/t/config-center/yaml/route-plugin.t new file mode 100644 index 000000000000..8bb5c0ff7954 --- /dev/null +++ b/t/config-center/yaml/route-plugin.t @@ -0,0 +1,90 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + +sub read_file($) { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $yaml_config = read_file("conf/config.yaml"); +$yaml_config =~ s/node_listen: 9080/node_listen: 1984/; +$yaml_config =~ s/config_center: etcd/config_center: yaml/; +$yaml_config =~ s/enable_admin: true/enable_admin: false/; + +run_tests(); + +__DATA__ + +=== TEST 1: route with plugin +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /hello + plugins: + proxy-rewrite: + uri: /uri/plugin_proxy_rewrite + headers: + X-Api-Version: v2 + upstream: + nodes: + "127.0.0.1:1980": 1 + type: roundrobin +#END +--- request +GET /hello +--- more_headers +X-Api-Version:v1 +--- response_body +uri: /uri/plugin_proxy_rewrite +host: localhost +x-api-version: v2 +x-real-ip: 127.0.0.1 +--- no_error_log +[error] + + +=== TEST 2: route with invalid plugin +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /hello + plugins: + proxy-rewrite: + headers: + "": "" + upstream: + nodes: + "127.0.0.1:1980": 1 + type: roundrobin +#END +--- request +GET /hello +--- error_code: 404 +--- error_log +failed to check the configuration of plugin proxy-rewrite err: invalid type as header value, context: ngx.timer diff --git a/t/config-center-yaml/route-service.t b/t/config-center/yaml/route-service.t similarity index 100% rename from t/config-center-yaml/route-service.t rename to t/config-center/yaml/route-service.t diff --git a/t/config-center-yaml/route-upstream.t b/t/config-center/yaml/route-upstream.t similarity index 100% rename from t/config-center-yaml/route-upstream.t rename to t/config-center/yaml/route-upstream.t diff --git a/t/config-center-yaml/route.t b/t/config-center/yaml/route.t similarity index 100% rename from t/config-center-yaml/route.t rename to t/config-center/yaml/route.t