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(response-ratelimiting) redis ssl #8595

Merged
Merged
Changes from 13 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
14 changes: 11 additions & 3 deletions kong/plugins/response-ratelimiting/policies/init.lua
Original file line number Diff line number Diff line change
@@ -55,11 +55,19 @@ local function get_redis_connection(conf)
local red = redis:new()
red:set_timeout(conf.redis_timeout)

sock_opts.ssl = conf.redis_ssl
sock_opts.ssl_verify = conf.redis_ssl_verify
sock_opts.server_name = conf.redis_server_name

-- use a special pool name only if redis_database is set to non-zero
-- otherwise use the default pool name host:port
sock_opts.pool = conf.redis_database and
conf.redis_host .. ":" .. conf.redis_port ..
":" .. conf.redis_database
if conf.redis_database ~= 0 then
sock_opts.pool = fmt( "%s:%d;%d",
conf.redis_host,
conf.redis_port,
conf.redis_database)
end

local ok, err = red:connect(conf.redis_host, conf.redis_port,
sock_opts)
if not ok then
3 changes: 3 additions & 0 deletions kong/plugins/response-ratelimiting/schema.lua
Original file line number Diff line number Diff line change
@@ -75,6 +75,9 @@ return {
{ redis_port = typedefs.port({ default = 6379 }), },
{ redis_password = { type = "string", len_min = 0, referenceable = true }, },
{ redis_username = { type = "string", referenceable = true }, },
{ redis_ssl = { type = "boolean", required = true, default = false, }, },
{ redis_ssl_verify = { type = "boolean", required = true, default = false }, },
{ redis_server_name = typedefs.sni },
{ redis_timeout = { type = "number", default = 2000 }, },
{ redis_database = { type = "number", default = 0 }, },
{ block_on_first_violation = { type = "boolean", required = true, default = false }, },
1,547 changes: 811 additions & 736 deletions spec/03-plugins/24-response-rate-limiting/04-access_spec.lua

Large diffs are not rendered by default.

438 changes: 244 additions & 194 deletions spec/03-plugins/24-response-rate-limiting/05-integration_spec.lua
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ local tostring = tostring

local REDIS_HOST = helpers.redis_host
local REDIS_PORT = helpers.redis_port
local REDIS_SSL_PORT = helpers.redis_ssl_port
local REDIS_SSL_SNI = helpers.redis_ssl_sni

local REDIS_DB_1 = 1
local REDIS_DB_2 = 2
@@ -71,185 +73,167 @@ describe("Plugin: rate-limiting (integration)", function()
helpers.stop_kong()
end)

describe("config.policy = redis", function()
-- Regression test for the following issue:
-- https://github.com/Kong/kong/issues/3292

lazy_setup(function()
flush_redis(red, REDIS_DB_1)
flush_redis(red, REDIS_DB_2)
flush_redis(red, REDIS_DB_3)
if red_version >= version("6.0.0") then
add_redis_user(red)
end

local route1 = assert(bp.routes:insert {
hosts = { "redistest1.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route1.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = REDIS_PORT,
redis_database = REDIS_DB_1,
fault_tolerant = false,
limits = { video = { minute = 6 } },
},
})

local route2 = assert(bp.routes:insert {
hosts = { "redistest2.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route2.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = REDIS_PORT,
redis_database = REDIS_DB_2,
fault_tolerant = false,
limits = { video = { minute = 6 } },
},
})

if red_version >= version("6.0.0") then
local route3 = assert(bp.routes:insert {
hosts = { "redistest3.com" },
local strategies = {
no_ssl = {
redis_port = REDIS_PORT,
},
ssl_verify = {
redis_ssl = true,
redis_ssl_verify = true,
redis_server_name = REDIS_SSL_SNI,
lua_ssl_trusted_certificate = "spec/fixtures/redis/ca.crt",
redis_port = REDIS_SSL_PORT,
},
ssl_no_verify = {
redis_ssl = true,
redis_ssl_verify = false,
redis_server_name = "really.really.really.does.not.exist.host.test",
redis_port = REDIS_SSL_PORT,
},
}

for strategy, config in pairs(strategies) do

describe("config.policy = redis #" .. strategy, function()
-- Regression test for the following issue:
-- https://github.com/Kong/kong/issues/3292

lazy_setup(function()
flush_redis(red, REDIS_DB_1)
flush_redis(red, REDIS_DB_2)
flush_redis(red, REDIS_DB_3)
if red_version >= version("6.0.0") then
add_redis_user(red)
end

bp = helpers.get_db_utils(nil, {
"routes",
"services",
"plugins",
}, {
"response-ratelimiting",
})

local route1 = assert(bp.routes:insert {
hosts = { "redistest1.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route3.id },
route = { id = route1.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = REDIS_PORT,
redis_username = REDIS_USER_VALID,
redis_password = REDIS_PASSWORD,
redis_database = REDIS_DB_3,
fault_tolerant = false,
limits = { video = { minute = 6 } },
policy = "redis",
redis_host = REDIS_HOST,
redis_port = config.redis_port,
redis_database = REDIS_DB_1,
redis_ssl = config.redis_ssl,
redis_ssl_verify = config.redis_ssl_verify,
redis_server_name = config.redis_server_name,
fault_tolerant = false,
redis_timeout = 10000,
limits = { video = { minute = 6 } },
},
})

local route4 = assert(bp.routes:insert {
hosts = { "redistest4.com" },
local route2 = assert(bp.routes:insert {
hosts = { "redistest2.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route4.id },
route = { id = route2.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = REDIS_PORT,
redis_username = REDIS_USER_INVALID,
redis_password = REDIS_PASSWORD,
redis_database = REDIS_DB_4,
fault_tolerant = false,
limits = { video = { minute = 6 } },
policy = "redis",
redis_host = REDIS_HOST,
redis_port = config.redis_port,
redis_database = REDIS_DB_2,
redis_ssl = config.redis_ssl,
redis_ssl_verify = config.redis_ssl_verify,
redis_server_name = config.redis_server_name,
fault_tolerant = false,
redis_timeout = 10000,
limits = { video = { minute = 6 } },
},
})
end

assert(helpers.start_kong({
nginx_conf = "spec/fixtures/custom_nginx.template",
}))
client = helpers.proxy_client()
end)
if red_version >= version("6.0.0") then
local route3 = assert(bp.routes:insert {
hosts = { "redistest3.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route3.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = config.redis_port,
redis_username = REDIS_USER_VALID,
redis_password = REDIS_PASSWORD,
redis_database = REDIS_DB_3,
redis_ssl = config.redis_ssl,
redis_ssl_verify = config.redis_ssl_verify,
redis_server_name = config.redis_server_name,
fault_tolerant = false,
redis_timeout = 10000,
limits = { video = { minute = 6 } },
},
})

local route4 = assert(bp.routes:insert {
hosts = { "redistest4.com" },
})
assert(bp.plugins:insert {
name = "response-ratelimiting",
route = { id = route4.id },
config = {
policy = "redis",
redis_host = REDIS_HOST,
redis_port = config.redis_port,
redis_username = REDIS_USER_INVALID,
redis_password = REDIS_PASSWORD,
redis_database = REDIS_DB_4,
redis_ssl = config.redis_ssl,
redis_ssl_verify = config.redis_ssl_verify,
redis_server_name = config.redis_server_name,
fault_tolerant = false,
redis_timeout = 10000,
limits = { video = { minute = 6 } },
},
})
end

assert(helpers.start_kong({
nginx_conf = "spec/fixtures/custom_nginx.template",
lua_ssl_trusted_certificate = config.lua_ssl_trusted_certificate,
}))
client = helpers.proxy_client()
end)

lazy_teardown(function()
helpers.stop_kong()
if red_version >= version("6.0.0") then
remove_redis_user(red)
end
end)

it("connection pool respects database setting", function()
assert(red:select(REDIS_DB_1))
local size_1 = assert(red:dbsize())

lazy_teardown(function()
if red_version >= version("6.0.0") then
remove_redis_user(red)
end
end)
assert(red:select(REDIS_DB_2))
local size_2 = assert(red:dbsize())

assert.equal(0, tonumber(size_1))
assert.equal(0, tonumber(size_2))
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

it("connection pool respects database setting", function()
assert(red:select(REDIS_DB_1))
local size_1 = assert(red:dbsize())

assert(red:select(REDIS_DB_2))
local size_2 = assert(red:dbsize())

assert.equal(0, tonumber(size_1))
assert.equal(0, tonumber(size_2))
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

local res = assert(client:send {
method = "GET",
path = "/response-headers?x-kong-limit=video=1",
headers = {
["Host"] = "redistest1.com"
}
})
assert.res_status(200, res)
assert.equal(6, tonumber(res.headers["x-ratelimit-limit-video-minute"]))
assert.equal(5, tonumber(res.headers["x-ratelimit-remaining-video-minute"]))

-- Wait for async timer to increment the limit

ngx.sleep(SLEEP_TIME)

assert(red:select(REDIS_DB_1))
size_1 = assert(red:dbsize())

assert(red:select(REDIS_DB_2))
size_2 = assert(red:dbsize())

-- TEST: DB 1 should now have one hit, DB 2 and 3 none

assert.is_true(tonumber(size_1) > 0)
assert.equal(0, tonumber(size_2))
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

-- response-ratelimiting plugin reuses the redis connection
local res = assert(client:send {
method = "GET",
path = "/response-headers?x-kong-limit=video=1",
headers = {
["Host"] = "redistest2.com"
}
})
assert.res_status(200, res)
assert.equal(6, tonumber(res.headers["x-ratelimit-limit-video-minute"]))
assert.equal(5, tonumber(res.headers["x-ratelimit-remaining-video-minute"]))

-- Wait for async timer to increment the limit

ngx.sleep(SLEEP_TIME)

assert(red:select(REDIS_DB_1))
size_1 = assert(red:dbsize())

assert(red:select(REDIS_DB_2))
size_2 = assert(red:dbsize())

-- TEST: DB 1 and 2 should now have one hit, DB 3 none

assert.is_true(tonumber(size_1) > 0)
assert.is_true(tonumber(size_2) > 0)
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

-- response-ratelimiting plugin reuses the redis connection
if red_version >= version("6.0.0") then
local res = assert(client:send {
method = "GET",
path = "/response-headers?x-kong-limit=video=1",
headers = {
["Host"] = "redistest3.com"
["Host"] = "redistest1.com"
}
})
assert.res_status(200, res)
@@ -266,49 +250,115 @@ describe("Plugin: rate-limiting (integration)", function()
assert(red:select(REDIS_DB_2))
size_2 = assert(red:dbsize())

assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())

-- TEST: All DBs should now have one hit, because the
-- plugin correctly chose to select the database it is
-- configured to hit
-- TEST: DB 1 should now have one hit, DB 2 and 3 none

assert.is_true(tonumber(size_1) > 0)
assert.is_true(tonumber(size_2) > 0)
assert.is_true(tonumber(size_3) > 0)
end
end)

it("authenticates and executes with a valid redis user having proper ACLs", function()
if red_version >= version("6.0.0") then
assert.equal(0, tonumber(size_2))
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

-- response-ratelimiting plugin reuses the redis connection
local res = assert(client:send {
method = "GET",
path = "/status/200",
path = "/response-headers?x-kong-limit=video=1",
headers = {
["Host"] = "redistest3.com"
["Host"] = "redistest2.com"
}
})
assert.res_status(200, res)
else
ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " ..
"'authenticates and executes with a valid redis user having proper ACLs' will be skipped")
end
end)
assert.equal(6, tonumber(res.headers["x-ratelimit-limit-video-minute"]))
assert.equal(5, tonumber(res.headers["x-ratelimit-remaining-video-minute"]))

it("fails to rate-limit for a redis user with missing ACLs", function()
if red_version >= version("6.0.0") then
local res = assert(client:send {
method = "GET",
path = "/status/200",
headers = {
["Host"] = "redistest4.com"
}
})
assert.res_status(500, res)
else
ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " ..
"'fails to response rate-limit for a redis user with missing ACLs' will be skipped")
end
-- Wait for async timer to increment the limit

ngx.sleep(SLEEP_TIME)

assert(red:select(REDIS_DB_1))
size_1 = assert(red:dbsize())

assert(red:select(REDIS_DB_2))
size_2 = assert(red:dbsize())

-- TEST: DB 1 and 2 should now have one hit, DB 3 none

assert.is_true(tonumber(size_1) > 0)
assert.is_true(tonumber(size_2) > 0)
if red_version >= version("6.0.0") then
assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())
assert.equal(0, tonumber(size_3))
end

-- response-ratelimiting plugin reuses the redis connection
if red_version >= version("6.0.0") then
local res = assert(client:send {
method = "GET",
path = "/response-headers?x-kong-limit=video=1",
headers = {
["Host"] = "redistest3.com"
}
})
assert.res_status(200, res)
assert.equal(6, tonumber(res.headers["x-ratelimit-limit-video-minute"]))
assert.equal(5, tonumber(res.headers["x-ratelimit-remaining-video-minute"]))

-- Wait for async timer to increment the limit

ngx.sleep(SLEEP_TIME)

assert(red:select(REDIS_DB_1))
size_1 = assert(red:dbsize())

assert(red:select(REDIS_DB_2))
size_2 = assert(red:dbsize())

assert(red:select(REDIS_DB_3))
local size_3 = assert(red:dbsize())

-- TEST: All DBs should now have one hit, because the
-- plugin correctly chose to select the database it is
-- configured to hit

assert.is_true(tonumber(size_1) > 0)
assert.is_true(tonumber(size_2) > 0)
assert.is_true(tonumber(size_3) > 0)
end
end)

it("authenticates and executes with a valid redis user having proper ACLs", function()
if red_version >= version("6.0.0") then
local res = assert(client:send {
method = "GET",
path = "/status/200",
headers = {
["Host"] = "redistest3.com"
}
})
assert.res_status(200, res)
else
ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " ..
"'authenticates and executes with a valid redis user having proper ACLs' will be skipped")
end
end)

it("fails to rate-limit for a redis user with missing ACLs", function()
if red_version >= version("6.0.0") then
local res = assert(client:send {
method = "GET",
path = "/status/200",
headers = {
["Host"] = "redistest4.com"
}
})
assert.res_status(500, res)
else
ngx.log(ngx.WARN, "Redis v" .. tostring(red_version) .. " does not support ACL functions " ..
"'fails to response rate-limit for a redis user with missing ACLs' will be skipped")
end
end)
end)
end)
end
end)