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 }, } }, }, }