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