Skip to content

Commit 3af5759

Browse files
fix: disallow creating duplicate plugins in global rules (#12800)
1 parent 6498ed6 commit 3af5759

File tree

6 files changed

+418
-118
lines changed

6 files changed

+418
-118
lines changed

apisix/admin/global_rules.lua

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ local resource = require("apisix.admin.resource")
1919
local schema_plugin = require("apisix.admin.plugins").check_schema
2020
local plugins_encrypt_conf = require("apisix.admin.plugins").encrypt_conf
2121

22+
local pairs = pairs
23+
local ipairs = ipairs
24+
local tostring = tostring
25+
26+
local function get_global_rules()
27+
local g = core.etcd.get("/global_rules", true)
28+
if not g then
29+
return nil
30+
end
31+
return core.table.try_read_attr(g, "body", "list")
32+
end
2233

2334
local function check_conf(id, conf, need_id, schema)
2435
local ok, err = core.schema.check(schema, conf)
@@ -31,6 +42,32 @@ local function check_conf(id, conf, need_id, schema)
3142
return nil, {error_msg = err}
3243
end
3344

45+
-- Check for plugin conflicts with existing global rules
46+
if conf.plugins then
47+
local global_rules = get_global_rules()
48+
if global_rules then
49+
for _, existing_rule in ipairs(global_rules) do
50+
-- Skip checking against itself when updating
51+
if existing_rule.value and existing_rule.value.id and
52+
tostring(existing_rule.value.id) ~= tostring(id) then
53+
54+
if existing_rule.value.plugins then
55+
-- Check for any overlapping plugins
56+
for plugin_name, _ in pairs(conf.plugins) do
57+
if existing_rule.value.plugins[plugin_name] then
58+
return nil, {
59+
error_msg = "plugin '" .. plugin_name ..
60+
"' already exists in global rule with id '" ..
61+
existing_rule.value.id .. "'"
62+
}
63+
end
64+
end
65+
end
66+
end
67+
end
68+
end
69+
end
70+
3471
return true
3572
end
3673

apisix/init.lua

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,8 @@ local function common_phase(phase_name)
468468
return
469469
end
470470

471-
plugin.run_global_rules(api_ctx, api_ctx.global_rules, phase_name)
471+
local global_rules, conf_version = apisix_global_rules.global_rules()
472+
plugin.run_global_rules(api_ctx, global_rules, conf_version, phase_name)
472473

473474
if api_ctx.script_obj then
474475
script.run(phase_name, api_ctx)
@@ -721,8 +722,8 @@ function _M.http_access_phase()
721722
local route = api_ctx.matched_route
722723
if not route then
723724
-- run global rule when there is no matching route
724-
local global_rules = apisix_global_rules.global_rules()
725-
plugin.run_global_rules(api_ctx, global_rules, nil)
725+
local global_rules, conf_version = apisix_global_rules.global_rules()
726+
plugin.run_global_rules(api_ctx, global_rules, conf_version, nil)
726727

727728
core.log.info("not find any matched route")
728729
return core.response.exit(404,
@@ -774,8 +775,8 @@ function _M.http_access_phase()
774775
api_ctx.route_name = route.value.name
775776

776777
-- run global rule
777-
local global_rules = apisix_global_rules.global_rules()
778-
plugin.run_global_rules(api_ctx, global_rules, nil)
778+
local global_rules, conf_version = apisix_global_rules.global_rules()
779+
plugin.run_global_rules(api_ctx, global_rules, conf_version, nil)
779780

780781
if route.value.script then
781782
script.load(route, api_ctx)

apisix/plugin.lua

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ local expr_lrucache = core.lrucache.new({
5959
local meta_pre_func_load_lrucache = core.lrucache.new({
6060
ttl = 300, count = 512
6161
})
62+
local merge_global_rule_lrucache = core.lrucache.new({
63+
ttl = 300, count = 512
64+
})
65+
6266
local local_conf
6367
local check_plugin_metadata
6468

@@ -1263,7 +1267,45 @@ function _M.set_plugins_meta_parent(plugins, parent)
12631267
end
12641268

12651269

1266-
function _M.run_global_rules(api_ctx, global_rules, phase_name)
1270+
local function merge_global_rules(global_rules, conf_version)
1271+
-- First pass: identify duplicate plugins across all global rules
1272+
local plugins_hash = {}
1273+
local seen_plugin = {}
1274+
local values = global_rules
1275+
for _, global_rule in config_util.iterate_values(values) do
1276+
if global_rule.value and global_rule.value.plugins then
1277+
for plugin_name, plugin_conf in pairs(global_rule.value.plugins) do
1278+
if seen_plugin[plugin_name] then
1279+
core.log.error("Found ", plugin_name,
1280+
" configured across different global rules.",
1281+
" Removing it from execution list")
1282+
plugins_hash[plugin_name] = nil
1283+
else
1284+
plugins_hash[plugin_name] = plugin_conf
1285+
seen_plugin[plugin_name] = true
1286+
end
1287+
end
1288+
end
1289+
end
1290+
1291+
local dummy_global_rule = {
1292+
key = "/apisix/global_rules/dummy",
1293+
value = {
1294+
updated_time = ngx.time(),
1295+
plugins = plugins_hash,
1296+
created_time = ngx.time(),
1297+
id = 1,
1298+
},
1299+
createdIndex = conf_version,
1300+
modifiedIndex = conf_version,
1301+
clean_handlers = {},
1302+
}
1303+
1304+
return dummy_global_rule
1305+
end
1306+
1307+
1308+
function _M.run_global_rules(api_ctx, global_rules, conf_version, phase_name)
12671309
if global_rules and #global_rules > 0 then
12681310
local orig_conf_type = api_ctx.conf_type
12691311
local orig_conf_version = api_ctx.conf_version
@@ -1273,22 +1315,26 @@ function _M.run_global_rules(api_ctx, global_rules, phase_name)
12731315
api_ctx.global_rules = global_rules
12741316
end
12751317

1318+
local dummy_global_rule = merge_global_rule_lrucache(conf_version,
1319+
global_rules,
1320+
merge_global_rules,
1321+
global_rules,
1322+
conf_version)
1323+
12761324
local plugins = core.tablepool.fetch("plugins", 32, 0)
1277-
local values = global_rules
12781325
local route = api_ctx.matched_route
1279-
for _, global_rule in config_util.iterate_values(values) do
1280-
api_ctx.conf_type = "global_rule"
1281-
api_ctx.conf_version = global_rule.modifiedIndex
1282-
api_ctx.conf_id = global_rule.value.id
1283-
1284-
core.table.clear(plugins)
1285-
plugins = _M.filter(api_ctx, global_rule, plugins, route)
1286-
if phase_name == nil then
1287-
_M.run_plugin("rewrite", plugins, api_ctx)
1288-
_M.run_plugin("access", plugins, api_ctx)
1289-
else
1290-
_M.run_plugin(phase_name, plugins, api_ctx)
1291-
end
1326+
api_ctx.conf_type = "global_rule"
1327+
api_ctx.conf_version = dummy_global_rule.modifiedIndex
1328+
api_ctx.conf_id = dummy_global_rule.value.id
1329+
1330+
core.table.clear(plugins)
1331+
plugins = _M.filter(api_ctx, dummy_global_rule, plugins, route)
1332+
1333+
if phase_name == nil then
1334+
_M.run_plugin("rewrite", plugins, api_ctx)
1335+
_M.run_plugin("access", plugins, api_ctx)
1336+
else
1337+
_M.run_plugin(phase_name, plugins, api_ctx)
12921338
end
12931339
core.tablepool.release("plugins", plugins)
12941340

0 commit comments

Comments
 (0)