kong/spec/03-plugins/23-rate-limiting/05-integration_spec.lua (292 lines of code) (raw):
local helpers = require "spec.helpers"
local redis = require "resty.redis"
local version = require "version"
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
local REDIS_DB_3 = 3
local REDIS_DB_4 = 4
local REDIS_USER_VALID = "ratelimit-user"
local REDIS_USER_INVALID = "some-user"
local REDIS_PASSWORD = "secret"
local SLEEP_TIME = 1
local function redis_connect()
local red = redis:new()
red:set_timeout(2000)
assert(red:connect(REDIS_HOST, REDIS_PORT))
local red_version = string.match(red:info(), 'redis_version:([%g]+)\r\n')
return red, assert(version(red_version))
end
local function flush_redis(red, db)
assert(red:select(db))
red:flushall()
end
local function add_redis_user(red)
assert(red:acl("setuser", REDIS_USER_VALID, "on", "allkeys", "+incrby", "+select", "+info", "+expire", "+get", "+exists", ">" .. REDIS_PASSWORD))
assert(red:acl("setuser", REDIS_USER_INVALID, "on", "allkeys", "+get", ">" .. REDIS_PASSWORD))
end
local function remove_redis_user(red)
assert(red:acl("deluser", REDIS_USER_VALID))
assert(red:acl("deluser", REDIS_USER_INVALID))
end
describe("Plugin: rate-limiting (integration)", function()
local client
local bp
local red
local red_version
lazy_setup(function()
bp = helpers.get_db_utils(nil, {
"routes",
"services",
"plugins",
}, {
"rate-limiting"
})
red, red_version = redis_connect()
end)
lazy_teardown(function()
if client then
client:close()
end
if red then
red:close()
end
helpers.stop_kong()
end)
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",
}, {
"rate-limiting"
})
local route1 = assert(bp.routes:insert {
hosts = { "redistest1.com" },
})
assert(bp.plugins:insert {
name = "rate-limiting",
route = { id = route1.id },
config = {
minute = 1,
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,
},
})
local route2 = assert(bp.routes:insert {
hosts = { "redistest2.com" },
})
assert(bp.plugins:insert {
name = "rate-limiting",
route = { id = route2.id },
config = {
minute = 1,
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,
},
})
if red_version >= version("6.0.0") then
local route3 = assert(bp.routes:insert {
hosts = { "redistest3.com" },
})
assert(bp.plugins:insert {
name = "rate-limiting",
route = { id = route3.id },
config = {
minute = 2, -- Handle multiple tests
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, -- ensure to not get a pooled authenticated connection by using a different db
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,
},
})
local route4 = assert(bp.routes:insert {
hosts = { "redistest4.com" },
})
assert(bp.plugins:insert {
name = "rate-limiting",
route = { id = route4.id },
config = {
minute = 1,
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, -- ensure to not get a pooled authenticated connection by using a different db
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,
},
})
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())
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 = "/status/200",
headers = {
["Host"] = "redistest1.com"
}
})
assert.res_status(200, res)
-- 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.equal(1, 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
-- rate-limiting plugin will reuses the redis connection
local res = assert(client:send {
method = "GET",
path = "/status/200",
headers = {
["Host"] = "redistest2.com"
}
})
assert.res_status(200, res)
-- 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.equal(1, tonumber(size_1))
assert.equal(1, 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
if red_version >= version("6.0.0") then
-- rate-limiting plugin will reuses the redis connection
local res = assert(client:send {
method = "GET",
path = "/status/200",
headers = {
["Host"] = "redistest3.com"
}
})
assert.res_status(200, res)
-- 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 rate-limit for a redis user with missing ACLs' will be skipped")
end
end)
end)
end
end)