222 lines
9.2 KiB
Lua
222 lines
9.2 KiB
Lua
local typedefs = require "kong.db.schema.typedefs"
|
|
local redis_schema = require "kong.tools.redis.schema"
|
|
|
|
local SYNC_RATE_REALTIME = -1
|
|
|
|
|
|
local ORDERED_PERIODS = { "second", "minute", "hour", "day", "month", "year"}
|
|
|
|
|
|
local function validate_periods_order(config)
|
|
for i, lower_period in ipairs(ORDERED_PERIODS) do
|
|
local v1 = config[lower_period]
|
|
if type(v1) == "number" then
|
|
for j = i + 1, #ORDERED_PERIODS do
|
|
local upper_period = ORDERED_PERIODS[j]
|
|
local v2 = config[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
|
|
|
|
if config.policy ~= "redis" and config.sync_rate ~= SYNC_RATE_REALTIME then
|
|
return nil, "sync_rate can only be used with the redis policy"
|
|
end
|
|
|
|
if config.policy == "redis" then
|
|
if config.sync_rate ~= SYNC_RATE_REALTIME and config.sync_rate < 0.02 then
|
|
return nil, "sync_rate must be greater than 0.02, or -1 to disable"
|
|
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",
|
|
len_min = 0,
|
|
one_of = {
|
|
"local",
|
|
"redis",
|
|
},
|
|
}
|
|
|
|
else
|
|
policy = { description = "The rate-limiting policies to use for retrieving and incrementing the limits.", type = "string",
|
|
default = "local",
|
|
len_min = 0,
|
|
one_of = {
|
|
"local",
|
|
"cluster",
|
|
"redis",
|
|
},
|
|
}
|
|
end
|
|
|
|
|
|
return {
|
|
name = "rate-limiting",
|
|
fields = {
|
|
{ protocols = typedefs.protocols_http },
|
|
{ config = {
|
|
type = "record",
|
|
fields = {
|
|
{ second = { description = "The number of HTTP requests that can be made per second.", type = "number", gt = 0 }, },
|
|
{ minute = { description = "The number of HTTP requests that can be made per minute.", type = "number", gt = 0 }, },
|
|
{ hour = { description = "The number of HTTP requests that can be made per hour.", type = "number", gt = 0 }, },
|
|
{ day = { description = "The number of HTTP requests that can be made per day.", type = "number", gt = 0 }, },
|
|
{ month = { description = "The number of HTTP requests that can be made per month.", type = "number", gt = 0 }, },
|
|
{ year = { description = "The number of HTTP requests that can be made per year.", type = "number", gt = 0 }, },
|
|
{ limit_by = { description = "The entity that is used when aggregating the limits.", type = "string",
|
|
default = "consumer",
|
|
one_of = { "consumer", "credential", "ip", "service", "header", "path" },
|
|
}, },
|
|
{ header_name = typedefs.header_name },
|
|
{ path = typedefs.path },
|
|
{ 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 data store. If `true`, requests will be proxied anyway, effectively disabling the rate-limiting function until the data store is working again. If `false`, then the clients will see `500` errors.", type = "boolean", required = true, default = true }, },
|
|
{ redis = redis_schema.config_schema },
|
|
{ hide_client_headers = { description = "Optionally hide informative response headers.", type = "boolean", required = true, default = false }, },
|
|
{ error_code = { description = "Set a custom error code to return when the rate limit is exceeded.", type = "number", default = 429, gt = 0 }, },
|
|
{ error_message = { description = "Set a custom error message to return when the rate limit is exceeded.", type = "string", default = "API rate limit exceeded" }, },
|
|
{ sync_rate = { description = "How often to sync counter data to the central data store. A value of -1 results in synchronous behavior.", type = "number", required = true, default = -1 }, },
|
|
},
|
|
custom_validator = validate_periods_order,
|
|
shorthand_fields = {
|
|
-- TODO: deprecated forms, to be removed in Kong 4.0
|
|
{ redis_host = {
|
|
type = "string",
|
|
deprecation = {
|
|
replaced_with = { { path = { 'redis', 'host' } } },
|
|
message = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = "rate-limiting: 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 = {
|
|
{ at_least_one_of = { "config.second", "config.minute", "config.hour", "config.day", "config.month", "config.year" } },
|
|
{ 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 },
|
|
} },
|
|
{ conditional = {
|
|
if_field = "config.limit_by", if_match = { eq = "header" },
|
|
then_field = "config.header_name", then_match = { required = true },
|
|
} },
|
|
{ conditional = {
|
|
if_field = "config.limit_by", if_match = { eq = "path" },
|
|
then_field = "config.path", then_match = { required = true },
|
|
} },
|
|
},
|
|
}
|