Skip to content

Commit

Permalink
feat(wasm): support bundled filters (#12843)
Browse files Browse the repository at this point in the history
  • Loading branch information
flrgh authored Apr 24, 2024
1 parent 9db89af commit 1e90a31
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 13 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/wasm-bundled-filters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: Add `wasm_filters` configuration value for enabling individual filters
type: feature
scope: Configuration
27 changes: 27 additions & 0 deletions kong.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,33 @@
# top level are registered
# * This path _may_ be a symlink to a directory.

#wasm_filters = bundled,user # Comma-separated list of Wasm filters to be made
# available for use in filter chains.
#
# When the `off` keyword is specified as the
# only value, no filters will be available for use.
#
# When the `bundled` keyword is specified, all filters
# bundled with Kong will be available.
#
# When the `user` keyword is specified, all filters
# within the `wasm_filters_path` will be available.
#
# **Examples:**
#
# - `wasm_filters = bundled,user` enables _all_ bundled
# and user-supplied filters
# - `wasm_filters = user` enables _only_ user-supplied
# filters
# - `wasm_filters = filter-a,filter-b` enables _only_
# filters named `filter-a` or `filter-b` (whether
# bundled _or_ user-suppplied)
#
# If a conflict occurs where a bundled filter and a
# user-supplied filter share the same name, a warning
# will be logged, and the user-supplied filter will
# be used instead.

#------------------------------------------------------------------------------
# WASM injected directives
#------------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions kong/conf_loader/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ local CONF_PARSERS = {

wasm = { typ = "boolean" },
wasm_filters_path = { typ = "string" },
wasm_filters = { typ = "array" },

error_template_html = { typ = "string" },
error_template_json = { typ = "string" },
Expand Down Expand Up @@ -640,4 +641,6 @@ return {
_NOP_TOSTRING_MT = _NOP_TOSTRING_MT,

LMDB_VALIDATION_TAG = LMDB_VALIDATION_TAG,

WASM_BUNDLED_FILTERS_PATH = "/usr/local/kong/wasm",
}
88 changes: 84 additions & 4 deletions kong/conf_loader/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local socket_url = require "socket.url"
local conf_constants = require "kong.conf_loader.constants"
local listeners = require "kong.conf_loader.listeners"
local conf_parse = require "kong.conf_loader.parse"
local nginx_signals = require "kong.cmd.utils.nginx_signals"
local pl_pretty = require "pl.pretty"
local pl_config = require "pl.config"
local pl_file = require "pl.file"
Expand Down Expand Up @@ -171,11 +172,12 @@ local function load_config_file(path)
end

--- Get available Wasm filters list
-- @param[type=string] Path where Wasm filters are stored.
---@param filters_path string # Path where Wasm filters are stored.
---@return kong.configuration.wasm_filter[]
local function get_wasm_filters(filters_path)
local wasm_filters = {}

if filters_path then
if filters_path and pl_path.isdir(filters_path) then
local filter_files = {}
for entry in pl_path.dir(filters_path) do
local pathname = pl_path.join(filters_path, entry)
Expand Down Expand Up @@ -547,8 +549,86 @@ local function load(path, custom_conf, opts)

-- Wasm module support
if conf.wasm then
local wasm_filters = get_wasm_filters(conf.wasm_filters_path)
conf.wasm_modules_parsed = setmetatable(wasm_filters, conf_constants._NOP_TOSTRING_MT)
---@type table<string, boolean>
local allowed_filter_names = {}
local all_bundled_filters_enabled = false
local all_user_filters_enabled = false
local all_filters_disabled = false
for _, filter in ipairs(conf.wasm_filters) do
if filter == "bundled" then
all_bundled_filters_enabled = true

elseif filter == "user" then
all_user_filters_enabled = true

elseif filter == "off" then
all_filters_disabled = true

else
allowed_filter_names[filter] = true
end
end

if all_filters_disabled then
allowed_filter_names = {}
all_bundled_filters_enabled = false
all_user_filters_enabled = false
end

---@type table<string, kong.configuration.wasm_filter>
local active_filters_by_name = {}

local bundled_filter_path = conf_constants.WASM_BUNDLED_FILTERS_PATH
if not pl_path.isdir(bundled_filter_path) then
local alt_path

local nginx_bin = nginx_signals.find_nginx_bin(conf)
if nginx_bin then
alt_path = pl_path.dirname(nginx_bin) .. "/../../../kong/wasm"
alt_path = pl_path.normpath(alt_path) or alt_path
end

if alt_path and pl_path.isdir(alt_path) then
log.debug("loading bundled proxy-wasm filters from alt path: %s",
alt_path)
bundled_filter_path = alt_path

else
log.warn("Bundled proxy-wasm filters path (%s) does not exist " ..
"or is not a directory. Bundled filters may not be " ..
"available", bundled_filter_path)
end
end

conf.wasm_bundled_filters_path = bundled_filter_path
local bundled_filters = get_wasm_filters(bundled_filter_path)
for _, filter in ipairs(bundled_filters) do
if all_bundled_filters_enabled or allowed_filter_names[filter.name] then
active_filters_by_name[filter.name] = filter
end
end

local user_filters = get_wasm_filters(conf.wasm_filters_path)
for _, filter in ipairs(user_filters) do
if all_user_filters_enabled or allowed_filter_names[filter.name] then
if active_filters_by_name[filter.name] then
log.warn("Replacing bundled filter %s with a user-supplied " ..
"filter at %s", filter.name, filter.path)
end
active_filters_by_name[filter.name] = filter
end
end

---@type kong.configuration.wasm_filter[]
local active_filters = {}
for _, filter in pairs(active_filters_by_name) do
insert(active_filters, filter)
end
sort(active_filters, function(lhs, rhs)
return lhs.name < rhs.name
end)

conf.wasm_modules_parsed = setmetatable(active_filters, conf_constants._NOP_TOSTRING_MT)

local function add_wasm_directive(directive, value, prefix)
local directive_name = (prefix or "") .. directive
Expand Down
2 changes: 1 addition & 1 deletion kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ local constants = {
exit = "kong",
service = "upstream",
}
}
},
}

for _, v in ipairs(constants.CLUSTERING_SYNC_STATUS) do
Expand Down
1 change: 1 addition & 0 deletions kong/templates/kong_defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ tracing_sampling_rate = 0.01
wasm = off
wasm_filters_path = NONE
wasm_dynamic_module = NONE
wasm_filters = bundled,user
request_debug = on
request_debug_token =
Expand Down
122 changes: 115 additions & 7 deletions spec/01-unit/03-conf_loader_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1948,10 +1948,47 @@ describe("Configuration loader", function()

describe("#wasm properties", function()
local temp_dir, cleanup
local user_filters
local bundled_filters
local all_filters

lazy_setup(function()
temp_dir, cleanup = helpers.make_temp_dir()
assert(helpers.file.write(temp_dir .. "/empty-filter.wasm", "hello!"))
assert(helpers.file.write(temp_dir .. "/filter-a.wasm", "hello!"))
assert(helpers.file.write(temp_dir .. "/filter-b.wasm", "hello!"))

user_filters = {
{
name = "filter-a",
path = temp_dir .. "/filter-a.wasm",
},
{
name = "filter-b",
path = temp_dir .. "/filter-b.wasm",
}
}

do
-- for local builds, the bundled filter path is not constant, so we
-- must load the config first to discover the path
local conf = assert(conf_loader(nil, {
wasm = "on",
wasm_filters = "bundled",
}))

assert(conf.wasm_bundled_filters_path)
bundled_filters = {
{
name = "datakit",
path = conf.wasm_bundled_filters_path .. "/datakit.wasm",
},
}
end

all_filters = {}
table.insert(all_filters, bundled_filters[1])
table.insert(all_filters, user_filters[1])
table.insert(all_filters, user_filters[2])
end)

lazy_teardown(function() cleanup() end)
Expand Down Expand Up @@ -1979,12 +2016,7 @@ describe("Configuration loader", function()
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same({
{
name = "empty-filter",
path = temp_dir .. "/empty-filter.wasm",
}
}, conf.wasm_modules_parsed)
assert.same(all_filters, conf.wasm_modules_parsed)
assert.same(temp_dir, conf.wasm_filters_path)
end)

Expand All @@ -1997,6 +2029,82 @@ describe("Configuration loader", function()
assert.is_nil(conf)
end)

it("wasm_filters default", function()
local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same(all_filters, conf.wasm_modules_parsed)
assert.same({ "bundled", "user" }, conf.wasm_filters)
end)

it("wasm_filters = off", function()
local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters = "off",
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same({}, conf.wasm_modules_parsed)
end)

it("wasm_filters = 'user' allows all user filters", function()
local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters = "user",
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same(user_filters, conf.wasm_modules_parsed)
end)

it("wasm_filters can allow individual user filters", function()
local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters = assert(user_filters[1].name),
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same({ user_filters[1] }, conf.wasm_modules_parsed)
end)

it("wasm_filters = 'bundled' allows all bundled filters", function()
local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters = "bundled",
wasm_filters_path = temp_dir,
})
assert.is_nil(err)
assert.same(bundled_filters, conf.wasm_modules_parsed)
end)

it("prefers user filters to bundled filters when a conflict exists", function()
local user_filter = temp_dir .. "/datakit.wasm"
assert(helpers.file.write(user_filter, "I'm a happy little wasm filter"))
finally(function()
assert(os.remove(user_filter))
end)

local conf, err = conf_loader(nil, {
wasm = "on",
wasm_filters = "bundled,user",
wasm_filters_path = temp_dir,
})
assert.is_nil(err)

local found = false
for _, filter in ipairs(conf.wasm_modules_parsed) do
if filter.name == "datakit" then
found = true
assert.equals(user_filter, filter.path,
"user filter should override the bundled filter")
end
end

assert.is_true(found, "expected the user filter to be enabled")
end)

end)

describe("errors", function()
Expand Down
18 changes: 18 additions & 0 deletions spec/02-integration/20-wasm/06-clustering_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe("#wasm - hybrid mode #postgres", function()
local cp_filter_path

local dp_prefix = "dp"
local dp_errlog = dp_prefix .. "/logs/error.log"

lazy_setup(function()
helpers.clean_prefix(cp_prefix)
Expand Down Expand Up @@ -108,8 +109,16 @@ describe("#wasm - hybrid mode #postgres", function()
cluster_listen = "127.0.0.1:9005",
nginx_conf = "spec/fixtures/custom_nginx.template",
wasm = true,
wasm_filters = "user", -- don't enable bundled filters for this test
wasm_filters_path = cp_filter_path,
nginx_main_worker_processes = 2,
}))

assert.logfile(cp_errlog).has.line([[successfully loaded "response_transformer" module]], true, 10)
assert.logfile(cp_errlog).has.no.line("[error]", true, 0)
assert.logfile(cp_errlog).has.no.line("[alert]", true, 0)
assert.logfile(cp_errlog).has.no.line("[crit]", true, 0)
assert.logfile(cp_errlog).has.no.line("[emerg]", true, 0)
end)

lazy_teardown(function()
Expand Down Expand Up @@ -138,10 +147,18 @@ describe("#wasm - hybrid mode #postgres", function()
admin_listen = "off",
nginx_conf = "spec/fixtures/custom_nginx.template",
wasm = true,
wasm_filters = "user", -- don't enable bundled filters for this test
wasm_filters_path = dp_filter_path,
node_id = node_id,
nginx_main_worker_processes = 2,
}))

assert.logfile(dp_errlog).has.line([[successfully loaded "response_transformer" module]], true, 10)
assert.logfile(dp_errlog).has.no.line("[error]", true, 0)
assert.logfile(dp_errlog).has.no.line("[alert]", true, 0)
assert.logfile(dp_errlog).has.no.line("[crit]", true, 0)
assert.logfile(dp_errlog).has.no.line("[emerg]", true, 0)

client = helpers.proxy_client()
end)

Expand Down Expand Up @@ -325,6 +342,7 @@ describe("#wasm - hybrid mode #postgres", function()
admin_listen = "off",
nginx_conf = "spec/fixtures/custom_nginx.template",
wasm = true,
wasm_filters = "user", -- don't enable bundled filters for this test
wasm_filters_path = tmp_dir,
node_id = node_id,
}))
Expand Down
2 changes: 1 addition & 1 deletion spec/02-integration/20-wasm/07-reports_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ for _, strategy in helpers.each_strategy() do
local _, reports_data = assert(reports_server:join())
reports_data = cjson.encode(reports_data)

assert.match("wasm_cnt=2", reports_data)
assert.match("wasm_cnt=3", reports_data)
end)

it("logs number of requests triggering a Wasm filter", function()
Expand Down
Loading

1 comment on commit 1e90a31

@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:1e90a3190eca6d2dd615dea07ba32b5104a96a93
Artifacts available https://github.com/Kong/kong/actions/runs/8819376411

Please sign in to comment.