Skip to content

Commit

Permalink
feat(wasm): add wasm plugin interface (#13843)
Browse files Browse the repository at this point in the history
  • Loading branch information
flrgh authored Nov 15, 2024
1 parent 8a295b8 commit 4059a31
Show file tree
Hide file tree
Showing 15 changed files with 774 additions and 139 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/wasm-filter-plugins.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: "**proxy-wasm**: Added support for Wasm filters to be configured via the /plugins admin API"
type: feature
scope: Core
2 changes: 2 additions & 0 deletions kong-3.9.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ build = {
["kong.runloop.plugin_servers.rpc.mp_rpc"] = "kong/runloop/plugin_servers/rpc/mp_rpc.lua",
["kong.runloop.plugin_servers.rpc.pb_rpc"] = "kong/runloop/plugin_servers/rpc/pb_rpc.lua",
["kong.runloop.wasm"] = "kong/runloop/wasm.lua",
["kong.runloop.wasm.plugins"] = "kong/runloop/wasm/plugins.lua",
["kong.runloop.wasm.properties"] = "kong/runloop/wasm/properties.lua",

["kong.workspaces"] = "kong/workspaces/init.lua",
Expand Down Expand Up @@ -284,6 +285,7 @@ build = {
["kong.db.schema.json"] = "kong/db/schema/json.lua",
["kong.db.schema.others.migrations"] = "kong/db/schema/others/migrations.lua",
["kong.db.schema.others.declarative_config"] = "kong/db/schema/others/declarative_config.lua",
["kong.db.schema.others.wasm_filter"] = "kong/db/schema/others/wasm_filter.lua",
["kong.db.schema.entity"] = "kong/db/schema/entity.lua",
["kong.db.schema.metaschema"] = "kong/db/schema/metaschema.lua",
["kong.db.schema.typedefs"] = "kong/db/schema/typedefs.lua",
Expand Down
8 changes: 8 additions & 0 deletions kong/conf_loader/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,14 @@ local function load(path, custom_conf, opts)
end
end

if conf.wasm_modules_parsed then
for _, filter in ipairs(conf.wasm_modules_parsed) do
assert(plugins[filter.name] == nil,
"duplicate plugin/wasm filter name: " .. filter.name)
plugins[filter.name] = true
end
end

conf.loaded_plugins = setmetatable(plugins, conf_constants._NOP_TOSTRING_MT)
end

Expand Down
8 changes: 8 additions & 0 deletions kong/db/dao/plugins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local DAO = require "kong.db.dao"
local plugin_loader = require "kong.db.schema.plugin_loader"
local reports = require "kong.reports"
local plugin_servers = require "kong.runloop.plugin_servers"
local wasm_plugins = require "kong.runloop.wasm.plugins"
local version = require "version"
local load_module_if_exists = require "kong.tools.module".load_module_if_exists

Expand Down Expand Up @@ -167,6 +168,13 @@ local load_plugin_handler do
end
end

if not ok then
ok, handler = wasm_plugins.load_plugin(plugin)
if type(handler) == "table" then
handler._wasm = true
end
end

if not ok then
return nil, plugin .. " plugin is enabled but not installed;\n" .. handler
end
Expand Down
59 changes: 3 additions & 56 deletions kong/db/schema/entities/filter_chains.lua
Original file line number Diff line number Diff line change
@@ -1,74 +1,21 @@
local typedefs = require "kong.db.schema.typedefs"
local filter = require "kong.db.schema.others.wasm_filter"
local wasm = require "kong.runloop.wasm"
local constants = require "kong.constants"
local json_schema = require "kong.db.schema.json"


---@class kong.db.schema.entities.filter_chain : table
---
---@field id string
---@field name string|nil
---@field enabled boolean
---@field route table|nil
---@field service table|nil
---@field route { id: string }|nil
---@field service { id: string }|nil
---@field created_at number
---@field updated_at number
---@field tags string[]
---@field filters kong.db.schema.entities.wasm_filter[]


---@class kong.db.schema.entities.wasm_filter : table
---
---@field name string
---@field enabled boolean
---@field config any|nil


local filter_config_schema = {
parent_subschema_key = "name",
namespace = constants.SCHEMA_NAMESPACES.PROXY_WASM_FILTERS,
optional = true,
default = {
["$schema"] = json_schema.DRAFT_4,
-- filters with no user-defined JSON schema may accept an optional
-- config, but only as a string
type = { "string", "null" },
},
}


if kong and kong.configuration and kong.configuration.role == "data_plane" then
-- data plane nodes are not guaranteed to have access to filter metadata, so
-- they will use a JSON schema that permits all data types
--
-- this branch can be removed if we decide to turn off entity validation in
-- the data plane altogether
filter_config_schema = {
inline = {
["$schema"] = json_schema.DRAFT_4,
type = { "array", "boolean", "integer", "null", "number", "object", "string" },
},
}
end


local filter = {
type = "record",
fields = {
{ name = { type = "string", required = true, one_of = wasm.filter_names,
err = "no such filter", }, },
{ enabled = { type = "boolean", default = true, required = true, }, },

{ config = {
type = "json",
required = false,
json_schema = filter_config_schema,
},
},

},
}

return {
name = "filter_chains",
primary_key = { "id" },
Expand Down
89 changes: 63 additions & 26 deletions kong/db/schema/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ local random_string = require("kong.tools.rand").random_string
local uuid = require("kong.tools.uuid").uuid
local json_validate = json.validate

local EMPTY = {}

local Schema = {}
Schema.__index = Schema
Expand Down Expand Up @@ -1076,6 +1077,9 @@ end
-- @return true if compatible, false otherwise.
local function compatible_fields(f1, f2)
local t1, t2 = f1.type, f2.type
if t1 == "record" and t2 == "json" then
return true
end
if t1 ~= t2 then
return false
end
Expand Down Expand Up @@ -1128,6 +1132,59 @@ local function resolve_field(self, k, field, subschema)
end


---@param field table
---@param field_name string
---@param input table
---@return kong.db.schema.json.schema_doc? schema
---@return string? error
local function get_json_schema(field, field_name, input)
local json_schema = field.json_schema

local schema = json_schema.inline
if schema then
return schema
end

local parent_key = json_schema.parent_subschema_key
local subschema_key = input[parent_key]

if subschema_key then
local schema_name = json_schema.namespace .. "/" .. subschema_key
schema = json.get_schema(schema_name) or json_schema.default

if schema then
return schema

elseif not json_schema.optional then
return nil, validation_errors.JSON_SCHEMA_NOT_FOUND:format(schema_name)
end

elseif not json_schema.optional then
return nil, validation_errors.JSON_PARENT_KEY_MISSING:format(field_name, parent_key)
end

-- no error: schema is optional
end


---@param field table # Lua schema definition for this field
---@param field_name string
---@param input table # full input table that this field appears in
---@return boolean? ok
---@return string? error
local function validate_json_field(field, field_name, input)
local schema, err = get_json_schema(field, field_name, input)
if schema then
return json_validate(input[field_name], schema)

elseif err then
return nil, err
end

return true
end


--- Validate fields of a table, individually, against the schema.
-- @param self The schema
-- @param input The input table.
Expand All @@ -1141,37 +1198,17 @@ validate_fields = function(self, input)
local errors, _ = {}

local subschema = get_subschema(self, input)
local subschema_fields = subschema and subschema.fields or EMPTY

for k, v in pairs(input) do
local err
local field = self.fields[tostring(k)]
local subschema_field = subschema_fields[tostring(k)]

if field and field.type == "json" then
local json_schema = field.json_schema
local inline_schema = json_schema.inline

if inline_schema then
_, errors[k] = json_validate(v, inline_schema)

else
local parent_key = json_schema.parent_subschema_key
local json_subschema_key = input[parent_key]

if json_subschema_key then
local schema_name = json_schema.namespace .. "/" .. json_subschema_key
inline_schema = json.get_schema(schema_name) or json_schema.default

if inline_schema then
_, errors[k] = json_validate(v, inline_schema)

elseif not json_schema.optional then
errors[k] = validation_errors.JSON_SCHEMA_NOT_FOUND:format(schema_name)
end

elseif not json_schema.optional then
errors[k] = validation_errors.JSON_PARENT_KEY_MISSING:format(k, parent_key)
end
end
if field and field.type == "json"
or (subschema_field and subschema_field.type == "json")
then
_, errors[k] = validate_json_field(subschema_field or field, k, input)

elseif field and field.type == "self" then
local pok
Expand Down
5 changes: 2 additions & 3 deletions kong/db/schema/json.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ local DRAFT_4 = DRAFT_4_NO_FRAGMENT .. "#"
_M.DRAFT_4 = DRAFT_4


---@type table<string, table>
---@type table<string, kong.db.schema.json.schema_doc>
local schemas = {}


Expand Down Expand Up @@ -165,7 +165,7 @@ end
-- Retrieve a schema from local storage by name.
--
---@param name string
---@return table|nil schema
---@return kong.db.schema.json.schema_doc? schema
function _M.get_schema(name)
return schemas[name]
end
Expand All @@ -175,7 +175,6 @@ end
-- Remove a schema from local storage by name (if it exists).
--
---@param name string
---@return table|nil schema
function _M.remove_schema(name)
schemas[name] = nil
end
Expand Down
59 changes: 59 additions & 0 deletions kong/db/schema/others/wasm_filter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
local constants = require "kong.constants"
local json_schema = require "kong.db.schema.json"
local wasm = require "kong.runloop.wasm"


---@class kong.db.schema.entities.wasm_filter : table
---
---@field name string
---@field enabled boolean
---@field config any|nil


local filter_config_schema = {
parent_subschema_key = "name",
namespace = constants.SCHEMA_NAMESPACES.PROXY_WASM_FILTERS,
optional = true,
default = {
["$schema"] = json_schema.DRAFT_4,
-- filters with no user-defined JSON schema may accept an optional
-- config, but only as a string
type = { "string", "null" },
},
}


-- FIXME: this is clunky and error-prone because a harmless refactor might
-- affect whether this file is require()-ed before or after `kong.configuration`
-- is initialized
if kong and kong.configuration and kong.configuration.role == "data_plane" then
-- data plane nodes are not guaranteed to have access to filter metadata, so
-- they will use a JSON schema that permits all data types
--
-- this branch can be removed if we decide to turn off entity validation in
-- the data plane altogether
filter_config_schema = {
inline = {
["$schema"] = json_schema.DRAFT_4,
type = { "array", "boolean", "integer", "null", "number", "object", "string" },
},
}
end


return {
type = "record",
fields = {
{ name = { type = "string", required = true, one_of = wasm.filter_names,
err = "no such filter", }, },
{ enabled = { type = "boolean", default = true, required = true, }, },

{ config = {
type = "json",
required = false,
json_schema = filter_config_schema,
},
},

},
}
4 changes: 4 additions & 0 deletions kong/db/schema/plugin_loader.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local MetaSchema = require "kong.db.schema.metaschema"
local Entity = require "kong.db.schema.entity"
local plugin_servers = require "kong.runloop.plugin_servers"
local wasm_plugins = require "kong.runloop.wasm.plugins"
local is_array = require "kong.tools.table".is_array
local load_module_if_exists = require "kong.tools.module".load_module_if_exists

Expand All @@ -18,6 +19,9 @@ function plugin_loader.load_subschema(parent_schema, plugin, errors)
if not ok then
ok, schema = plugin_servers.load_schema(plugin)
end
if not ok then
ok, schema = wasm_plugins.load_schema(plugin)
end

if not ok then
return nil, "no configuration schema found for plugin: " .. plugin
Expand Down
Loading

1 comment on commit 4059a31

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bazel Build

Docker image available kong/kong:4059a31a5869433f13d3f4f4e50f91e7cf20c3e2
Artifacts available https://github.com/Kong/kong/actions/runs/11863132691

Please sign in to comment.