Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router/atc): extend route.snis to support wildcard for the leftmost or rightmost character with traditional-compatible flavor #12809

Merged
merged 18 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion kong/db/schema/entities/routes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ local entity_checks = {
}},
}

local snis_elements_type = typedefs.wildcard_host

if kong_router_flavor == "traditional" then
chronolaw marked this conversation as resolved.
Show resolved Hide resolved
snis_elements_type = typedefs.sni
end

-- works with both `traditional_compatible` and `expressions` routes
local validate_route
Expand Down Expand Up @@ -159,7 +164,7 @@ local routes = {

{ snis = { type = "set",
description = "A list of SNIs that match this Route.",
elements = typedefs.sni }, },
elements = snis_elements_type }, },
{ sources = typedefs.sources },
{ destinations = typedefs.destinations },

Expand Down
39 changes: 33 additions & 6 deletions kong/router/transform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,40 @@ do
end


local function sni_val_transform(_, p)
if #p > 1 and byte(p, -1) == DOT then
-- last dot in FQDNs must not be used for routing
return p:sub(1, -2)
local function sni_op_transform(sni)
chronolaw marked this conversation as resolved.
Show resolved Hide resolved
local op = OP_EQUAL

if byte(sni) == ASTERISK then
-- postfix matching
op = OP_POSTFIX

elseif byte(sni, -1) == ASTERISK then
-- prefix matching
op = OP_PREFIX
end

return p
return op
end


local function sni_val_transform(op, sni)
-- prefix matching
if op == OP_PREFIX then
sni = sni:sub(1, -2)

else
if #sni > 1 and byte(sni, -1) == DOT then
-- last dot in FQDNs must not be used for routing
sni = sni:sub(1, -2)
end
chronolaw marked this conversation as resolved.
Show resolved Hide resolved

-- postfix matching
if op == OP_POSTFIX then
sni = sni:sub(2)
end
end

return sni
end


Expand Down Expand Up @@ -329,7 +356,7 @@ local function get_expression(route)

expr_buf:reset()

local gen = gen_for_field("tls.sni", OP_EQUAL, snis, sni_val_transform)
local gen = gen_for_field("tls.sni", sni_op_transform, snis, sni_val_transform)
if gen then
-- See #6425, if `net.protocol` is not `https`
-- then SNI matching should simply not be considered
Expand Down
47 changes: 47 additions & 0 deletions spec/01-unit/01-db/01-schema/06-routes_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,53 @@ describe("routes schema (flavor = " .. flavor .. ")", function()
assert.truthy(ok)
assert.is_nil(errs)
end)

describe("'snis' matching attribute (wildcard)", function()
local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" }

it("accepts leftmost wildcard", function()
for _, sni in ipairs({ "*.example.org", "*.foo.bar.test" }) do
local route = Routes:process_auto_fields({
protocols = { "https" },
snis = { sni },
service = s,
}, "insert")
local ok, errs = Routes:validate(route)
assert.is_nil(errs)
assert.truthy(ok)
end
end)

it("accepts rightmost wildcard", function()
for _, sni in ipairs({ "example.*", "foo.bar.*" }) do
local route = Routes:process_auto_fields({
protocols = { "https" },
snis = { sni },
service = s,
}, "insert")
local ok, errs = Routes:validate(route)
assert.is_nil(errs)
assert.truthy(ok)
end
end)

it("rejects invalid wildcard", function()
for _, sni in ipairs({ "foo.*.test", "foo*.test" }) do
local route = Routes:process_auto_fields({
protocols = { "https" },
snis = { sni },
service = s,
}, "insert")
local ok, errs = Routes:validate(route)
assert.falsy(ok)
assert.same({
snis = {
"wildcard must be leftmost or rightmost character",
},
}, errs)
end
end)
end)
end)
end -- flavor in ipairs({ "traditional_compatible", "expressions" })

Expand Down
57 changes: 57 additions & 0 deletions spec/01-unit/08-router_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,63 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
end)
end)

if flavor ~= "traditional" then
describe("[wildcard sni]", function()
local use_case, router

lazy_setup(function()
use_case = {
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
snis = { "*.sni.test" },
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
snis = { "sni.*" },
},
},
}

router = assert(new_router(use_case))
end)

it("matches leftmost wildcards", function()
for _, sni in ipairs({"foo.sni.test", "foo.bar.sni.test"}) do
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
sni)
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)
assert.same(nil, match_t.matches.method)
assert.same(nil, match_t.matches.uri)
assert.same(nil, match_t.matches.uri_captures)
end
end)

it("matches rightmost wildcards", function()
for _, sni in ipairs({"sni.foo", "sni.foo.bar"}) do
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
sni)
assert.truthy(match_t)
assert.same(use_case[2].route, match_t.route)
end
end)

it("doesn't match wildcard", function()
for _, sni in ipairs({"bar.sni.foo", "foo.sni.test.bar"}) do
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
sni)
assert.is_nil(match_t)
end
end)
end)
end -- if flavor ~= "traditional" then


if flavor ~= "traditional" then
describe("incremental rebuild", function()
local router
Expand Down
Loading
Loading