From 35bc85102e005334d082800a11e491fff02e626c Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Tue, 26 Nov 2024 11:27:59 -0800 Subject: [PATCH] fix(plugins): load wasm filter plugins before external plugins In 4059a31a5869433f13d3f4f4e50f91e7cf20c3e2 (#13843) we added a plugin-like interface for Wasm filters. We now have 3 sources for plugins: Lua, External, and Wasm Filters. When a plugin is enabled or configured, the plugin system follows a resolution order for looking up the plugin handler and schema: 1. Lua => `require kong.plugins..{handler,schema}` 2. External => `kong.runloop.plugin_servers.load_{handler,schema}()` 3. Wasm Filters => `kong.runloop.wasm.plugins.load_{handler,schema}()` When a user configures Kong with a "bad" entry in `pluginserver_names` (read: a plugin server that is not actually installed), step #2 of the plugin resolution process throws an exception, because the external plugin subsystem attempts to query a plugin server that does not exist. Importantly, *this exception is not encountered when the user has only configured/enabled Lua plugins,* because we never reach beyond step #1 of the plugin resolution process. A side effect of adding the Wasm filter plugin interface is that discovered Wasm filters are added to the global plugins table (`kong.configuration.loaded_plugins`) when Wasm is enabled. This means that, if Wasm is enabled, and any Wasm filters are installed, we _always_ step through step #2 of the plugin resolution process, triggering an exception if the user has any badly-configured plugin server. A future change will likely render this scenario unreachable by performing deeper validation of `pluginserver_names` at startup. For now, a simple fix is just to change the resolution order such that Wasm filters are loaded _before_ we query the external plugin subsystem: 1. Lua 2. Wasm Filters 3. External --- kong/db/dao/plugins.lua | 8 +- kong/db/schema/plugin_loader.lua | 4 +- .../10-external-plugins/03-wasm_spec.lua | 96 +++++++++++++++++++ 3 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 spec/02-integration/10-external-plugins/03-wasm_spec.lua diff --git a/kong/db/dao/plugins.lua b/kong/db/dao/plugins.lua index 34199eaeaa46..0a851f0bb371 100644 --- a/kong/db/dao/plugins.lua +++ b/kong/db/dao/plugins.lua @@ -162,16 +162,16 @@ local load_plugin_handler do local plugin_handler = "kong.plugins." .. plugin .. ".handler" local ok, handler = load_module_if_exists(plugin_handler) if not ok then - ok, handler = plugin_servers.load_plugin(plugin) + ok, handler = wasm_plugins.load_plugin(plugin) if type(handler) == "table" then - handler._go = true + handler._wasm = true end end if not ok then - ok, handler = wasm_plugins.load_plugin(plugin) + ok, handler = plugin_servers.load_plugin(plugin) if type(handler) == "table" then - handler._wasm = true + handler._go = true end end diff --git a/kong/db/schema/plugin_loader.lua b/kong/db/schema/plugin_loader.lua index ec1964c09ddf..b1b73d950b17 100644 --- a/kong/db/schema/plugin_loader.lua +++ b/kong/db/schema/plugin_loader.lua @@ -17,10 +17,10 @@ function plugin_loader.load_subschema(parent_schema, plugin, errors) local plugin_schema = "kong.plugins." .. plugin .. ".schema" local ok, schema = load_module_if_exists(plugin_schema) if not ok then - ok, schema = plugin_servers.load_schema(plugin) + ok, schema = wasm_plugins.load_schema(plugin) end if not ok then - ok, schema = wasm_plugins.load_schema(plugin) + ok, schema = plugin_servers.load_schema(plugin) end if not ok then diff --git a/spec/02-integration/10-external-plugins/03-wasm_spec.lua b/spec/02-integration/10-external-plugins/03-wasm_spec.lua new file mode 100644 index 000000000000..494b487e8087 --- /dev/null +++ b/spec/02-integration/10-external-plugins/03-wasm_spec.lua @@ -0,0 +1,96 @@ +local helpers = require "spec.helpers" + +for _, strategy in helpers.each_strategy() do + +describe("external plugins and wasm #" .. strategy, function() + describe("wasm enabled in conjunction with unused pluginservers", function() + it("does not prevent kong from starting", function() + require("kong.runloop.wasm").enable({ + { name = "tests", + path = helpers.test_conf.wasm_filters_path .. "/tests.wasm", + }, + }) + + local bp = assert(helpers.get_db_utils(strategy, { + "services", + "routes", + "plugins", + "filter_chains", + }, { "response-transformer", "tests" })) + + local route = assert(bp.routes:insert({ + protocols = { "http" }, + paths = { "/" }, + service = assert(bp.services:insert({})), + })) + + -- configure a wasm filter plugin + assert(bp.plugins:insert({ + name = "tests", + route = route, + config = "", + })) + + -- configure a lua plugin + assert(bp.plugins:insert({ + name = "response-transformer", + route = route, + config = { + add = { + headers = { + "X-Lua-Plugin:hello from response-transformer", + }, + }, + }, + })) + + assert(helpers.start_kong({ + nginx_conf = "spec/fixtures/custom_nginx.template", + database = strategy, + + wasm = true, + wasm_filters = "tests", + + plugins = "response-transformer", + + -- this pluginserver does not exist, but we will validate that kong can + -- start so long as there are no configured/enabled plugins that will + -- require us to invoke it in any way + -- + -- XXX: this configuration could be considered invalid, and future changes + -- to plugin resolution/pluginserver code MAY opt to change this behavior + pluginserver_names = "not-exists", + pluginserver_not_exists_start_cmd = "/i/do/not/exist", + pluginserver_not_exists_query_cmd = "/i/do/not/exist", + })) + + local client + + finally(function() + if client then + client:close() + end + + helpers.stop_kong() + end) + + client = helpers.proxy_client() + local res = client:get("/", { + headers = { + ["X-PW-Test"] = "local_response", + ["X-PW-Input"] = "hello from wasm", + }, + }) + + -- verify that our wasm filter ran + local body = assert.res_status(200, res) + assert.equals("hello from wasm", body) + + -- verify that our lua plugin (response-transformer) ran + local header = assert.response(res).has.header("X-Lua-Plugin") + assert.equals("hello from response-transformer", header) + end) + end) +end) + +end -- each strategy