Skip to content

Commit 99106e5

Browse files
fix(admin): harden stream_route superior_id dependency checks (#13672)
1 parent 809455e commit 99106e5

2 files changed

Lines changed: 103 additions & 4 deletions

File tree

apisix/admin/stream_routes.lua

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ local function check_conf(id, conf, need_id, schema, opts)
6565

6666
if conf.protocol and conf.protocol.superior_id and not opts.skip_references_check then
6767
local superior_id = conf.protocol.superior_id
68+
if id and tostring(superior_id) == tostring(id) then
69+
return nil, {error_msg = "stream route can not set itself as superior_id"}
70+
end
71+
6872
local key = "/stream_routes/" .. superior_id
6973
local res, err = core.etcd.get(key)
7074
if not res then
@@ -79,7 +83,12 @@ local function check_conf(id, conf, need_id, schema, opts)
7983

8084
local superior_route = res.body.node.value
8185
if type(superior_route) == "string" then
82-
superior_route = core.json.decode(superior_route)
86+
local decoded, decode_err = core.json.decode(superior_route)
87+
if not decoded then
88+
return nil, {error_msg = "failed to decode stream routes[" .. superior_id
89+
.. "]: " .. decode_err}
90+
end
91+
superior_route = decoded
8392
end
8493

8594
if superior_route and superior_route.protocol
@@ -103,11 +112,11 @@ local function delete_checker(id)
103112
local key = "/stream_routes"
104113
local res, err = core.etcd.get(key, {prefix = true})
105114
if not res then
106-
return nil, {error_msg = "failed to fetch stream routes: " .. err}
115+
return 503, {error_msg = "failed to fetch stream routes: " .. err}
107116
end
108117

109118
if res.status ~= 200 then
110-
return nil, {error_msg = "failed to fetch stream routes, response code: " .. res.status}
119+
return 503, {error_msg = "failed to fetch stream routes, response code: " .. res.status}
111120
end
112121

113122
local nodes = res.body.list
@@ -124,7 +133,12 @@ local function delete_checker(id)
124133
for _, item in ipairs(nodes) do
125134
local route = item.value
126135
if type(route) == "string" then
127-
route = core.json.decode(route)
136+
local decoded, decode_err = core.json.decode(route)
137+
if not decoded then
138+
return 503, {error_msg = "failed to decode stream route [" .. tostring(item.key)
139+
.. "]: " .. decode_err}
140+
end
141+
route = decoded
128142
end
129143

130144
if route and route.protocol and tostring(route.protocol.superior_id) == id then

t/admin/stream-routes-subordinate.t

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,88 @@ passed
259259
GET /t
260260
--- response_body
261261
passed
262+
263+
264+
265+
=== TEST 8: superior_id references itself (should fail)
266+
--- config
267+
location /t {
268+
content_by_lua_block {
269+
local t = require("lib.test_admin").test
270+
local json = require("toolkit.json")
271+
local code, body = t('/apisix/admin/stream_routes/10',
272+
ngx.HTTP_PUT,
273+
[[{
274+
"protocol": {"name": "redis", "superior_id": "10"},
275+
"upstream": {
276+
"nodes": {"127.0.0.1:6379": 1},
277+
"type": "roundrobin"
278+
}
279+
}]]
280+
)
281+
if code ~= 400 then
282+
ngx.say("failed: expected 400, got ", code)
283+
return
284+
end
285+
local data = json.decode(body)
286+
if not data or not data.error_msg then
287+
ngx.say("failed: unexpected body: ", body)
288+
return
289+
end
290+
if not string.find(data.error_msg, "itself as superior_id", 1, true) then
291+
ngx.say("failed: unexpected body: ", body)
292+
return
293+
end
294+
ngx.say("passed")
295+
}
296+
}
297+
--- request
298+
GET /t
299+
--- response_body
300+
passed
301+
302+
303+
304+
=== TEST 9: force delete a referenced superior route (should succeed)
305+
--- config
306+
location /t {
307+
content_by_lua_block {
308+
local t = require("lib.test_admin").test
309+
local code = t('/apisix/admin/stream_routes/20',
310+
ngx.HTTP_PUT,
311+
[[{
312+
"protocol": {"name": "redis"},
313+
"upstream": {"nodes": {"127.0.0.1:6379": 1}, "type": "roundrobin"}
314+
}]]
315+
)
316+
if code >= 300 then
317+
ngx.say("failed to create superior: ", code)
318+
return
319+
end
320+
code = t('/apisix/admin/stream_routes/21',
321+
ngx.HTTP_PUT,
322+
[[{
323+
"protocol": {"name": "redis", "superior_id": "20"},
324+
"upstream": {"nodes": {"127.0.0.1:6380": 1}, "type": "roundrobin"}
325+
}]]
326+
)
327+
if code >= 300 then
328+
ngx.say("failed to create subordinate: ", code)
329+
return
330+
end
331+
-- force bypasses the reference check
332+
local dcode, body = t('/apisix/admin/stream_routes/20?force=true',
333+
ngx.HTTP_DELETE
334+
)
335+
if dcode >= 300 then
336+
ngx.say("failed to force delete: ", dcode, " ", body)
337+
return
338+
end
339+
t('/apisix/admin/stream_routes/21', ngx.HTTP_DELETE)
340+
ngx.say("passed")
341+
}
342+
}
343+
--- request
344+
GET /t
345+
--- response_body
346+
passed

0 commit comments

Comments
 (0)