This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 99106e53e fix(admin): harden stream_route superior_id dependency
checks (#13672)
99106e53e is described below
commit 99106e53e1c48aac3a0849c01deda36c8095fa87
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Wed Jul 8 19:35:38 2026 +0800
fix(admin): harden stream_route superior_id dependency checks (#13672)
---
apisix/admin/stream_routes.lua | 22 ++++++++--
t/admin/stream-routes-subordinate.t | 85 +++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/apisix/admin/stream_routes.lua b/apisix/admin/stream_routes.lua
index 104f26160..d4a79ac88 100644
--- a/apisix/admin/stream_routes.lua
+++ b/apisix/admin/stream_routes.lua
@@ -65,6 +65,10 @@ local function check_conf(id, conf, need_id, schema, opts)
if conf.protocol and conf.protocol.superior_id and not
opts.skip_references_check then
local superior_id = conf.protocol.superior_id
+ if id and tostring(superior_id) == tostring(id) then
+ return nil, {error_msg = "stream route can not set itself as
superior_id"}
+ end
+
local key = "/stream_routes/" .. superior_id
local res, err = core.etcd.get(key)
if not res then
@@ -79,7 +83,12 @@ local function check_conf(id, conf, need_id, schema, opts)
local superior_route = res.body.node.value
if type(superior_route) == "string" then
- superior_route = core.json.decode(superior_route)
+ local decoded, decode_err = core.json.decode(superior_route)
+ if not decoded then
+ return nil, {error_msg = "failed to decode stream routes[" ..
superior_id
+ .. "]: " .. decode_err}
+ end
+ superior_route = decoded
end
if superior_route and superior_route.protocol
@@ -103,11 +112,11 @@ local function delete_checker(id)
local key = "/stream_routes"
local res, err = core.etcd.get(key, {prefix = true})
if not res then
- return nil, {error_msg = "failed to fetch stream routes: " .. err}
+ return 503, {error_msg = "failed to fetch stream routes: " .. err}
end
if res.status ~= 200 then
- return nil, {error_msg = "failed to fetch stream routes, response
code: " .. res.status}
+ return 503, {error_msg = "failed to fetch stream routes, response
code: " .. res.status}
end
local nodes = res.body.list
@@ -124,7 +133,12 @@ local function delete_checker(id)
for _, item in ipairs(nodes) do
local route = item.value
if type(route) == "string" then
- route = core.json.decode(route)
+ local decoded, decode_err = core.json.decode(route)
+ if not decoded then
+ return 503, {error_msg = "failed to decode stream route [" ..
tostring(item.key)
+ .. "]: " .. decode_err}
+ end
+ route = decoded
end
if route and route.protocol and tostring(route.protocol.superior_id)
== id then
diff --git a/t/admin/stream-routes-subordinate.t
b/t/admin/stream-routes-subordinate.t
index 6ed4caa94..25a6279c3 100644
--- a/t/admin/stream-routes-subordinate.t
+++ b/t/admin/stream-routes-subordinate.t
@@ -259,3 +259,88 @@ passed
GET /t
--- response_body
passed
+
+
+
+=== TEST 8: superior_id references itself (should fail)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local json = require("toolkit.json")
+ local code, body = t('/apisix/admin/stream_routes/10',
+ ngx.HTTP_PUT,
+ [[{
+ "protocol": {"name": "redis", "superior_id": "10"},
+ "upstream": {
+ "nodes": {"127.0.0.1:6379": 1},
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code ~= 400 then
+ ngx.say("failed: expected 400, got ", code)
+ return
+ end
+ local data = json.decode(body)
+ if not data or not data.error_msg then
+ ngx.say("failed: unexpected body: ", body)
+ return
+ end
+ if not string.find(data.error_msg, "itself as superior_id", 1,
true) then
+ ngx.say("failed: unexpected body: ", body)
+ return
+ end
+ ngx.say("passed")
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 9: force delete a referenced superior route (should succeed)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code = t('/apisix/admin/stream_routes/20',
+ ngx.HTTP_PUT,
+ [[{
+ "protocol": {"name": "redis"},
+ "upstream": {"nodes": {"127.0.0.1:6379": 1}, "type":
"roundrobin"}
+ }]]
+ )
+ if code >= 300 then
+ ngx.say("failed to create superior: ", code)
+ return
+ end
+ code = t('/apisix/admin/stream_routes/21',
+ ngx.HTTP_PUT,
+ [[{
+ "protocol": {"name": "redis", "superior_id": "20"},
+ "upstream": {"nodes": {"127.0.0.1:6380": 1}, "type":
"roundrobin"}
+ }]]
+ )
+ if code >= 300 then
+ ngx.say("failed to create subordinate: ", code)
+ return
+ end
+ -- force bypasses the reference check
+ local dcode, body = t('/apisix/admin/stream_routes/20?force=true',
+ ngx.HTTP_DELETE
+ )
+ if dcode >= 300 then
+ ngx.say("failed to force delete: ", dcode, " ", body)
+ return
+ end
+ t('/apisix/admin/stream_routes/21', ngx.HTTP_DELETE)
+ ngx.say("passed")
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed