252 lines
8.9 KiB
Lua
252 lines
8.9 KiB
Lua
local typedefs = require "kong.db.schema.typedefs"
|
|
local redis_schema = require "kong.tools.redis.schema"
|
|
|
|
local ORDERED_PERIODS = { "second", "minute", "hour", "day", "month", "year" }
|
|
|
|
|
|
local function validate_periods_order(limit)
|
|
for i, lower_period in ipairs(ORDERED_PERIODS) do
|
|
local v1 = limit[lower_period]
|
|
if type(v1) == "number" then
|
|
for j = i + 1, #ORDERED_PERIODS do
|
|
local upper_period = ORDERED_PERIODS[j]
|
|
local v2 = limit[upper_period]
|
|
if type(v2) == "number" and v2 < v1 then
|
|
return nil, string.format("the limit for %s(%.1f) cannot be lower than the limit for %s(%.1f)",
|
|
upper_period, v2, lower_period, v1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
local function is_dbless()
|
|
local _, database, role = pcall(function()
|
|
return kong.configuration.database,
|
|
kong.configuration.role
|
|
end)
|
|
|
|
return database == "off" or role == "control_plane"
|
|
end
|
|
|
|
|
|
local policy
|
|
if is_dbless() then
|
|
policy = {
|
|
description =
|
|
"The rate-limiting policies to use for retrieving and incrementing the limits.",
|
|
type = "string",
|
|
default = "local",
|
|
one_of = {
|
|
"local",
|
|
"redis",
|
|
},
|
|
}
|
|
else
|
|
policy = {
|
|
description =
|
|
"The rate-limiting policies to use for retrieving and incrementing the limits.",
|
|
type = "string",
|
|
default = "local",
|
|
one_of = {
|
|
"local",
|
|
"cluster",
|
|
"redis",
|
|
},
|
|
}
|
|
end
|
|
|
|
return {
|
|
name = "response-ratelimiting",
|
|
fields = {
|
|
{ protocols = typedefs.protocols_http },
|
|
{
|
|
config = {
|
|
type = "record",
|
|
fields = {
|
|
{
|
|
header_name = {
|
|
description = "The name of the response header used to increment the counters.",
|
|
type = "string",
|
|
default = "x-kong-limit"
|
|
},
|
|
},
|
|
{
|
|
limit_by = {
|
|
description =
|
|
"The entity that will be used when aggregating the limits: `consumer`, `credential`, `ip`. If the `consumer` or the `credential` cannot be determined, the system will always fallback to `ip`.",
|
|
type = "string",
|
|
default = "consumer",
|
|
one_of = { "consumer", "credential", "ip" },
|
|
},
|
|
},
|
|
{ policy = policy },
|
|
{
|
|
fault_tolerant = {
|
|
description =
|
|
"A boolean value that determines if the requests should be proxied even if Kong has troubles connecting a third-party datastore. If `true`, requests will be proxied anyway, effectively disabling the rate-limiting function until the datastore is working again. If `false`, then the clients will see `500` errors.",
|
|
type = "boolean",
|
|
required = true,
|
|
default = true
|
|
},
|
|
},
|
|
{ redis = redis_schema.config_schema },
|
|
{
|
|
block_on_first_violation = {
|
|
description =
|
|
"A boolean value that determines if the requests should be blocked as soon as one limit is being exceeded. This will block requests that are supposed to consume other limits too.",
|
|
type = "boolean",
|
|
required = true,
|
|
default = false
|
|
},
|
|
},
|
|
{
|
|
hide_client_headers = {
|
|
description = "Optionally hide informative response headers.",
|
|
type = "boolean",
|
|
required = true,
|
|
default = false
|
|
},
|
|
},
|
|
{
|
|
limits = {
|
|
description = "A map that defines rate limits for the plugin.",
|
|
type = "map",
|
|
required = true,
|
|
len_min = 1,
|
|
keys = { type = "string" },
|
|
values = {
|
|
type = "record",
|
|
required = true,
|
|
fields = {
|
|
{ second = { type = "number", gt = 0 }, },
|
|
{ minute = { type = "number", gt = 0 }, },
|
|
{ hour = { type = "number", gt = 0 }, },
|
|
{ day = { type = "number", gt = 0 }, },
|
|
{ month = { type = "number", gt = 0 }, },
|
|
{ year = { type = "number", gt = 0 }, },
|
|
},
|
|
custom_validator = validate_periods_order,
|
|
entity_checks = {
|
|
{ at_least_one_of = ORDERED_PERIODS },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
shorthand_fields = {
|
|
-- TODO: deprecated forms, to be removed in Kong 4.0
|
|
{ redis_host = {
|
|
type = "string",
|
|
deprecation = {
|
|
replaced_with = { { path = { 'redis', 'host' } } },
|
|
message = "response-ratelimiting: config.redis_host is deprecated, please use config.redis.host instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { host = value } }
|
|
end
|
|
} },
|
|
{ redis_port = {
|
|
type = "integer",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'port'} } },
|
|
message = "response-ratelimiting: config.redis_port is deprecated, please use config.redis.port instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { port = value } }
|
|
end
|
|
} },
|
|
{ redis_password = {
|
|
type = "string",
|
|
len_min = 0,
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'password'} } },
|
|
message = "response-ratelimiting: config.redis_password is deprecated, please use config.redis.password instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { password = value } }
|
|
end
|
|
} },
|
|
{ redis_username = {
|
|
type = "string",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'username'} } },
|
|
message = "response-ratelimiting: config.redis_username is deprecated, please use config.redis.username instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { username = value } }
|
|
end
|
|
} },
|
|
{ redis_ssl = {
|
|
type = "boolean",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'ssl'} } },
|
|
message = "response-ratelimiting: config.redis_ssl is deprecated, please use config.redis.ssl instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { ssl = value } }
|
|
end
|
|
} },
|
|
{ redis_ssl_verify = {
|
|
type = "boolean",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'ssl_verify'} } },
|
|
message = "response-ratelimiting: config.redis_ssl_verify is deprecated, please use config.redis.ssl_verify instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { ssl_verify = value } }
|
|
end
|
|
} },
|
|
{ redis_server_name = {
|
|
type = "string",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'server_name'} } },
|
|
message = "response-ratelimiting: config.redis_server_name is deprecated, please use config.redis.server_name instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { server_name = value } }
|
|
end
|
|
} },
|
|
{ redis_timeout = {
|
|
type = "integer",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'timeout'} } },
|
|
message = "response-ratelimiting: config.redis_timeout is deprecated, please use config.redis.timeout instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { timeout = value } }
|
|
end
|
|
} },
|
|
{ redis_database = {
|
|
type = "integer",
|
|
deprecation = {
|
|
replaced_with = { { path = {'redis', 'database'} } },
|
|
message = "response-ratelimiting: config.redis_database is deprecated, please use config.redis.database instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { redis = { database = value } }
|
|
end
|
|
} },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
entity_checks = {
|
|
{ conditional = {
|
|
if_field = "config.policy", if_match = { eq = "redis" },
|
|
then_field = "config.redis.host", then_match = { required = true },
|
|
} },
|
|
{ conditional = {
|
|
if_field = "config.policy", if_match = { eq = "redis" },
|
|
then_field = "config.redis.port", then_match = { required = true },
|
|
} },
|
|
{ conditional = {
|
|
if_field = "config.policy", if_match = { eq = "redis" },
|
|
then_field = "config.redis.timeout", then_match = { required = true },
|
|
} },
|
|
},
|
|
}
|