diff --git a/kong/db/schema/entities/upstreams.lua b/kong/db/schema/entities/upstreams.lua index cbe0d541475c..d2d067315067 100644 --- a/kong/db/schema/entities/upstreams.lua +++ b/kong/db/schema/entities/upstreams.lua @@ -49,12 +49,6 @@ local positive_int_or_zero = Schema.define { } -local header_name = Schema.define { - type = "string", - custom_validator = utils.validate_header_name, -} - - local healthchecks_defaults = { active = { timeout = 1, @@ -130,8 +124,8 @@ local r = { { name = { type = "string", required = true, unique = true, custom_validator = validate_name }, }, { hash_on = hash_on }, { hash_fallback = hash_on }, - { hash_on_header = header_name, }, - { hash_fallback_header = header_name, }, + { hash_on_header = typedefs.header_name, }, + { hash_fallback_header = typedefs.header_name, }, { hash_on_cookie = { type = "string", custom_validator = utils.validate_cookie_name }, }, { hash_on_cookie_path = typedefs.path{ default = "/", }, }, { slots = { type = "integer", default = 10000, between = { 10, 2^16 }, }, }, diff --git a/kong/db/schema/typedefs.lua b/kong/db/schema/typedefs.lua index a5ff6fd629a8..a9e2e66088dc 100644 --- a/kong/db/schema/typedefs.lua +++ b/kong/db/schema/typedefs.lua @@ -2,6 +2,7 @@ -- @module kong.db.schema.typedefs local utils = require "kong.tools.utils" local Schema = require("kong.db.schema") +local socket_url = require("socket.url") local match = string.match @@ -57,6 +58,25 @@ local function validate_name(name) end +local function validate_url(url) + local parsed_url, err = socket_url.parse(url) + + if not parsed_url then + return nil, "could not parse url. " .. err + end + + if not parsed_url.host then + return nil, "missing host in url" + end + + if not parsed_url.scheme then + return nil, "missing scheme in url" + end + + return true +end + + local typedefs = {} @@ -98,6 +118,16 @@ typedefs.path = Schema.define { custom_validator = validate_path, } +typedefs.url = Schema.define { + type = "string", + custom_validator = validate_url, +} + +typedefs.header_name = Schema.define { + type = "string", + custom_validator = utils.validate_header_name, +} + typedefs.timeout = Schema.define { type = "integer", diff --git a/kong/plugins/acl/schema.lua b/kong/plugins/acl/schema.lua index 8695da33890a..0d7fd47febc0 100644 --- a/kong/plugins/acl/schema.lua +++ b/kong/plugins/acl/schema.lua @@ -1,18 +1,23 @@ -local Errors = require "kong.dao.errors" +local typedefs = require "kong.db.schema.typedefs" + return { - no_consumer = true, + name = "acl", fields = { - whitelist = { type = "array" }, - blacklist = { type = "array" }, - hide_groups_header = { type = "boolean", default = false }, + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { whitelist = { type = "array", elements = { type = "string" }, }, }, + { blacklist = { type = "array", elements = { type = "string" }, }, }, + { hide_groups_header = { type = "boolean", default = false }, }, + } + } + } + }, + entity_checks = { + { only_one_of = { "config.whitelist", "config.blacklist" }, }, + { at_least_one_of = { "config.whitelist", "config.blacklist" }, }, }, - self_check = function(schema, plugin_t, dao, is_update) - if next(plugin_t.whitelist or {}) and next(plugin_t.blacklist or {}) then - return false, Errors.schema "You cannot set both a whitelist and a blacklist" - elseif not (next(plugin_t.whitelist or {}) or next(plugin_t.blacklist or {})) then - return false, Errors.schema "You must set at least a whitelist or blacklist" - end - return true - end } diff --git a/kong/plugins/aws-lambda/schema.lua b/kong/plugins/aws-lambda/schema.lua index 763b4d162564..26208baaec88 100644 --- a/kong/plugins/aws-lambda/schema.lua +++ b/kong/plugins/aws-lambda/schema.lua @@ -1,101 +1,49 @@ -local function check_status(status) - if status and (status < 100 or status > 999) then - return false, "unhandled_status must be within 100 - 999." - end +local REGIONS = { + "ap-northeast-1", "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", "eu-west-2", + "sa-east-1", + "us-east-1", "us-east-2", + "us-gov-west-1", + "us-west-1", "us-west-2", +} - return true -end return { + name = "aws-lambda", fields = { - timeout = { - type = "number", - default = 60000, - required = true, - }, - keepalive = { - type = "number", - default = 60000, - required = true, - }, - aws_key = { - type = "string", - required = true, - }, - aws_secret = { - type = "string", - required = true, - }, - aws_region = { - type = "string", - required = true, - enum = { - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "us-gov-west-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-south-1", - "ca-central-1", - "eu-central-1", - "eu-west-1", - "eu-west-2", - "sa-east-1", - }, - }, - function_name = { - type= "string", - required = true, - }, - qualifier = { - type = "string", - }, - invocation_type = { - type = "string", - required = true, - default = "RequestResponse", - enum = { - "RequestResponse", - "Event", - "DryRun", - } - }, - log_type = { - type = "string", - required = true, - default = "Tail", - enum = { - "Tail", - "None", - } - }, - port = { - type = "number", - default = 443, - }, - unhandled_status = { - type = "number", - func = check_status, - }, - forward_request_method = { - type = "boolean", - default = false, - }, - forward_request_uri = { - type = "boolean", - default = false, - }, - forward_request_headers = { - type = "boolean", - default = false, - }, - forward_request_body = { - type = "boolean", - default = false, - }, + { config = { + type = "record", + nullable = false, + fields = { + { timeout = { type = "number", required = true, default = 60000 }, }, + { keepalive = { type = "number", required = true, default = 60000 }, }, + { aws_key = { type = "string", required = true }, }, + { aws_secret = { type = "string", required = true }, }, + { aws_region = { type = "string", required = true, one_of = REGIONS }, }, + { function_name = { type= "string", required = true }, }, + { qualifier = { type = "string" }, }, + { invocation_type = { + type = "string", + required = true, + default = "RequestResponse", + one_of = { "RequestResponse", "Event", "DryRun" }, + }, }, + { log_type = { + type = "string", + required = true, + default = "Tail", + one_of = { "Tail", "None" }, + }, }, + { port = { type = "integer", default = 443 }, }, + { unhandled_status = { type = "integer", between = { 100, 999 }, }, }, + { forward_request_method = { type = "boolean", default = false }, }, + { forward_request_uri = { type = "boolean", default = false }, }, + { forward_request_headers = { type = "boolean", default = false }, }, + { forward_request_body = { type = "boolean", default = false }, }, + }, }, }, }, } diff --git a/kong/plugins/basic-auth/schema.lua b/kong/plugins/basic-auth/schema.lua index 8cd00edb4b18..cc02d9cb9089 100644 --- a/kong/plugins/basic-auth/schema.lua +++ b/kong/plugins/basic-auth/schema.lua @@ -1,17 +1,15 @@ -local utils = require "kong.tools.utils" - -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then - return true - end - - return false, "the anonymous user must be empty or a valid uuid" -end +local typedefs = require "kong.db.schema.typedefs" return { - no_consumer = true, + name = "basic-auth", fields = { - anonymous = {type = "string", default = "", func = check_user}, - hide_credentials = {type = "boolean", default = false} - } + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { anonymous = { type = "string", uuid = true, len_min = 0, default = "" }, }, + { hide_credentials = { type = "boolean", default = false }, }, + }, }, }, + }, } diff --git a/kong/plugins/bot-detection/schema.lua b/kong/plugins/bot-detection/schema.lua index ced8e8f8209e..43732fb59bba 100644 --- a/kong/plugins/bot-detection/schema.lua +++ b/kong/plugins/bot-detection/schema.lua @@ -1,44 +1,23 @@ -local re_match = ngx.re.match - -local check_regex = function(value) - if value then - for _, rule in ipairs(value) do - local _, err = re_match("just a string to test", rule) - if err then - return false, "value '" .. rule .. "' is not a valid regex" - end - end - end - return true -end +local typedefs = require "kong.db.schema.typedefs" return { - no_consumer = true, + name = "bot-detection", fields = { - whitelist = { - type = "array", - func = check_regex, - new_type = { - type = "array", - elements = { - type = "string", - match = ".*", - is_regex = true, - }, - default = {}, - } - }, - blacklist = { - type = "array", - func = check_regex, - new_type = { - type = "array", - elements = { - type = "string", - is_regex = true, - }, - default = {}, - } - }, - } + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { whitelist = { + type = "array", + elements = { type = "string", is_regex = true, match = ".*" }, + default = {}, + }, }, + { blacklist = { + type = "array", + elements = { type = "string", is_regex = true }, + default = {}, + }, }, + }, }, }, + }, } diff --git a/kong/plugins/correlation-id/schema.lua b/kong/plugins/correlation-id/schema.lua index bc267bf6773c..c07304423fbc 100644 --- a/kong/plugins/correlation-id/schema.lua +++ b/kong/plugins/correlation-id/schema.lua @@ -1,17 +1,15 @@ return { + name = "correlation-id", fields = { - header_name = { - type = "string", - default = "Kong-Request-ID" + config = { + type = "record", + nullable = false, + fields = { + { header_name = { type = "string", default = "Kong-Request-ID" }, }, + { generator = { type = "string", default = "uuid#counter", + one_of = { "uuid", "uuid#counter", "tracker" }, }, }, + { echo_downstream = { type = "boolean", default = false, }, }, + }, }, - generator = { - type = "string", - default = "uuid#counter", - enum = {"uuid", "uuid#counter", "tracker"} - }, - echo_downstream = { - type = "boolean", - default = false - } - } + }, } diff --git a/kong/plugins/cors/schema.lua b/kong/plugins/cors/schema.lua index 5f190d05c375..c1308f99b6b8 100644 --- a/kong/plugins/cors/schema.lua +++ b/kong/plugins/cors/schema.lua @@ -1,26 +1,41 @@ -local re_match = ngx.re.match +local typedefs = require "kong.db.schema.typedefs" +local is_regex = require("kong.db.schema").validators.is_regex -local check_regex = function(value) - if value and (#value > 1 or value[1] ~= "*") then - for _, origin in ipairs(value) do - local _, err = re_match("just a string to test", origin) - if err then - return false, "origin '" .. origin .. "' is not a valid regex" - end - end + +local function validate_asterisk_or_regex(value) + if value == "*" or is_regex(value) then + return true end - return true + return nil, string.format("'%s' is not a valid regex", tostring(value)) end + return { - no_consumer = true, + name = "cors", fields = { - origins = { type = "array", func = check_regex }, - headers = { type = "array" }, - exposed_headers = { type = "array" }, - methods = { type = "array", enum = { "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" } }, - max_age = { type = "number" }, - credentials = { type = "boolean", default = false }, - preflight_continue = { type = "boolean", default = false } - } + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { origins = { + type = "array", + elements = { + type = "string", + custom_validator = validate_asterisk_or_regex, + }, }, }, + { headers = { type = "array", elements = { type = "string" }, }, }, + { exposed_headers = { type = "array", elements = { type = "string" }, }, }, + { methods = { + type = "array", + elements = { + type = "string", + one_of = { "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" }, + }, }, }, + { max_age = { type = "number" }, }, + { credentials = { type = "boolean", default = false }, }, + { preflight_continue = { type = "boolean", default = false }, }, + }, }, }, + }, } + diff --git a/kong/plugins/datadog/schema.lua b/kong/plugins/datadog/schema.lua index 335d2844df25..f6b7603b3050 100644 --- a/kong/plugins/datadog/schema.lua +++ b/kong/plugins/datadog/schema.lua @@ -1,35 +1,32 @@ -local find = string.find -local pl_utils = require "pl.utils" - -local metrics = { - ["request_count"] = true, - ["latency"] = true, - ["request_size"] = true, - ["status_count"] = true, - ["response_size"] = true, - ["unique_users"] = true, - ["request_per_user"] = true, - ["upstream_latency"] = true, - ["kong_latency"] = true, - ["status_count_per_user"] = true, +local STAT_NAMES = { + "kong_latency", + "latency", + "request_count", + "request_per_user", + "request_size", + "response_size", + "status_count", + "status_count_per_user", + "unique_users", + "upstream_latency", } -local stat_types = { - ["gauge"] = true, - ["timer"] = true, - ["counter"] = true, - ["histogram"] = true, - ["meter"] = true, - ["set"] = true, +local STAT_TYPES = { + "counter", + "gauge", + "histogram", + "meter", + "set", + "timer", } -local consumer_identifiers = { - ["consumer_id"] = true, - ["custom_id"] = true, - ["username"] = true, +local CONSUMER_IDENTIFIERS = { + "consumer_id", + "custom_id", + "username", } -local default_metrics = { +local DEFAULT_METRICS = { { name = "request_count", stat_type = "counter", @@ -90,127 +87,56 @@ local default_metrics = { } --- entries must have colons to set the key and value apart -local function check_tag_value(value) - if value == nil then - return true - end - - for _, entry in ipairs(value) do - local ok = find(entry, ":") - if ok then - local _, next = pl_utils.splitv(entry, ':') - if not next or #next == 0 then - return nil, "key '" .. entry .. "' has no value" - end - end - end - - return true -end - -local function check_schema(value) - for _, entry in ipairs(value) do - - if not entry.name or not entry.stat_type then - return false, "name and stat_type must be defined for all stats" - end - - if not metrics[entry.name] then - return false, "unrecognized metric name: " .. entry.name - end - - if not stat_types[entry.stat_type] then - return false, "unrecognized stat_type: " .. entry.stat_type - end - - local tag_ok, tag_error = check_tag_value(entry.tags) - if not tag_ok then - return false, "malformed tags: " .. tag_error - .. ". Tags must be list of key[:value]" - end - - if entry.name == "unique_users" and entry.stat_type ~= "set" then - return false, "unique_users metric only works with stat_type 'set'" - end - - if (entry.stat_type == "counter" or entry.stat_type == "gauge") - and ((not entry.sample_rate) or (entry.sample_rate - and type(entry.sample_rate) ~= "number") - or (entry.sample_rate and entry.sample_rate < 1)) then - - return false, "sample rate must be defined for counters and gauges." - end - - if (entry.name == "status_count_per_user" - or entry.name == "request_per_user" or entry.name == "unique_users") - and not entry.consumer_identifier then - - return false, "consumer_identifier must be defined for metric " .. - entry.name - end - - if (entry.name == "status_count_per_user" - or entry.name == "request_per_user" - or entry.name == "unique_users") - and entry.consumer_identifier then - - if not consumer_identifiers[entry.consumer_identifier] then - - return false, "invalid consumer_identifier for metric '" .. - entry.name .. - "'. Choices are consumer_id, custom_id, and username" - end - end - - if (entry.name == "status_count" - or entry.name == "status_count_per_user" - or entry.name == "request_per_user") - and entry.stat_type ~= "counter" then - - return false, entry.name .. " metric only works with stat_type 'counter'" - end - end - - return true -end - - return { + name = "datadog", fields = { - host = { - required = true, - type = "string", - default = "localhost", - }, - port = { - required = true, - type = "number", - default = 8125, - }, - metrics = { - required = true, - type = "array", - default = default_metrics, - func = check_schema, - new_type = { - type = "array", - default = default_metrics, - elements = { - type = "record", - fields = { - { name = { type = "string", required = true } }, - { stat_type = { type = "string", required = true } }, - { tags = { type = "array", elements = { type = "string", match = "^.*[^:]$" } } }, - { sample_rate = { type = "number" } }, - { consumer_identifier = { type = "string" } }, - } - } - } - }, - prefix = { - type = "string", - default = "kong", - }, - } + { config = { + type = "record", + nullable = false, + default = { metrics = DEFAULT_METRICS }, + fields = { + { host = { type = "string", required = true, default = "localhost" }, }, + { port = { type = "integer", required = true, default = 8125 }, }, + { prefix = { type = "string", default = "kong" }, }, + { metrics = { + type = "array", + required = true, + default = DEFAULT_METRICS, + elements = { + type = "record", + fields = { + { name = { type = "string", required = true, one_of = STAT_NAMES }, }, + { stat_type = { type = "string", required = true, one_of = STAT_TYPES }, }, + { tags = { type = "array", elements = { type = "string", match = "^.*[^:]$" }, }, }, + { sample_rate = { type = "number", between = { 1, math.huge }, }, }, + { consumer_identifier = { type = "string", one_of = CONSUMER_IDENTIFIERS }, }, + }, + entity_checks = { + { conditional = { + if_field = "name", if_match = { eq = "unique_users" }, + then_field = "stat_type", then_match = { eq = "set" }, + }, }, + + { conditional = { + if_field = "stat_type", + if_match = { one_of = { "counter", "gauge" }, }, + then_field = "sample_rate", + then_match = { required = true }, + }, }, + + { conditional = { + if_field = "name", + if_match = { one_of = { "status_count_per_user", "request_per_user", "unique_users" }, }, + then_field = "consumer_identifier", + then_match = { required = true }, + }, }, + + { conditional = { + if_field = "name", + if_match = { one_of = { "status_count", "status_count_per_user", "request_per_user" }, }, + then_field = "stat_type", + then_match = { eq = "counter" }, + }, }, + }, }, }, }, }, }, }, }, } + diff --git a/kong/plugins/file-log/schema.lua b/kong/plugins/file-log/schema.lua index 7af2d65becd6..093605ead762 100644 --- a/kong/plugins/file-log/schema.lua +++ b/kong/plugins/file-log/schema.lua @@ -6,7 +6,7 @@ local function validate_file(value) if not pl_path.exists(value) then local ok, err = pl_file.write(value, "") if not ok then - return false, string.format("Cannot create file: %s", err) + return nil, string.format("Cannot create file: %s", err) end end @@ -14,8 +14,17 @@ local function validate_file(value) end return { + name = "file-log", fields = { - path = { required = true, type = "string", func = validate_file }, - reopen = { type = "boolean", default = false }, + { config = { + type = "record", + nullable = false, + fields = { + { path = { type = "string", + required = true, + custom_validator = validate_file, + }, }, + { reopen = { type = "boolean", default = false }, }, + }, }, }, } } diff --git a/kong/plugins/hmac-auth/schema.lua b/kong/plugins/hmac-auth/schema.lua index 5cff964ca073..b4a3445c94ed 100644 --- a/kong/plugins/hmac-auth/schema.lua +++ b/kong/plugins/hmac-auth/schema.lua @@ -1,35 +1,38 @@ -local utils = require "kong.tools.utils" +local typedefs = require "kong.db.schema.typedefs" -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then - return true - end - return false, "the anonymous user must be empty or a valid uuid" -end - -local function check_clock_skew_positive(v) - if v and v < 0 then - return false, "Clock Skew should be positive" - end - return true -end - -local algorithms = { +local ALGORITHMS = { "hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512", } + return { - no_consumer = true, + name = "hmac-auth", fields = { - hide_credentials = { type = "boolean", default = false }, - clock_skew = { type = "number", default = 300, func = check_clock_skew_positive }, - anonymous = { type = "string", default = "", func = check_user }, - validate_request_body = { type = "boolean", default = false }, - enforce_headers = { type = "array", default = {} }, - algorithms = { type = "array", default = algorithms, enum = algorithms } - } + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { hide_credentials = { type = "boolean", default = false }, }, + { clock_skew = { type = "number", default = 300, gt = 0 }, }, + { anonymous = { type = "string", uuid = true, len_min = 0, default = "" }, }, + { validate_request_body = { type = "boolean", default = false }, }, + { enforce_headers = { + type = "array", + elements = { type = "string" }, + default = {}, + }, }, + { algorithms = { + type = "array", + elements = { type = "string", one_of = ALGORITHMS }, + default = ALGORITHMS, + }, }, + }, + }, + }, + }, } diff --git a/kong/plugins/http-log/schema.lua b/kong/plugins/http-log/schema.lua index 3ffa03db6577..aeb3a14754d8 100644 --- a/kong/plugins/http-log/schema.lua +++ b/kong/plugins/http-log/schema.lua @@ -1,13 +1,20 @@ +local typedefs = require "kong.db.schema.typedefs" + return { + name = "http-log", fields = { - http_endpoint = { required = true, type = "url" }, - method = { default = "POST", enum = { "POST", "PUT", "PATCH" } }, - content_type = { default = "application/json", enum = { "application/json" } }, - timeout = { default = 10000, type = "number" }, - keepalive = { default = 60000, type = "number" }, - - retry_count = {type = "number", default = 10}, - queue_size = {type = "number", default = 1}, - flush_timeout = {type = "number", default = 2}, - } + { config = { + type = "record", + nullable = false, + fields = { + { http_endpoint = typedefs.url({ required = true }) }, + { method = { type = "string", default = "POST", one_of = { "POST", "PUT", "PATCH" }, }, }, + { content_type = { type = "string", default = "application/json", one_of = { "application/json" }, }, }, + { timeout = { type = "number", default = 10000 }, }, + { keepalive = { type = "number", default = 60000 }, }, + { retry_count = { type = "integer", default = 10 }, }, + { queue_size = { type = "integer", default = 1 }, }, + { flush_timeout = { type = "number", default = 2 }, }, + }, }, }, + }, } diff --git a/kong/plugins/ip-restriction/schema.lua b/kong/plugins/ip-restriction/schema.lua index e35ff2dd5c5e..73b9a7db326a 100644 --- a/kong/plugins/ip-restriction/schema.lua +++ b/kong/plugins/ip-restriction/schema.lua @@ -1,33 +1,33 @@ local iputils = require "resty.iputils" -local Errors = require "kong.dao.errors" -local function validate_ips(v, t, column) - if v and type(v) == "table" then - for _, ip in ipairs(v) do - local _, err = iputils.parse_cidr(ip) - if type(err) == "string" then -- It's an error only if the second variable is a string - return false, "cannot parse '" .. ip .. "': " .. err - end - end + +local function validate_ip(ip) + local _, err = iputils.parse_cidr(ip) + -- It's an error only if the second variable is a string + if type(err) == "string" then + return false, "cannot parse '" .. ip .. "': " .. err end return true end + +local ip = { type = "string", custom_validator = validate_ip } + + return { + name = "ip-restriction", fields = { - whitelist = {type = "array", func = validate_ips}, - blacklist = {type = "array", func = validate_ips} + config = { + type = "record", + nullable = false, + fields = { + { whitelist = { type = "array", elements = ip, }, }, + { blacklist = { type = "array", elements = ip, }, }, + }, + }, + }, + entity_checks = { + { only_one_of = { "config.whitelist", "config.blacklist" }, }, + { at_least_one_of = { "config.whitelist", "config.blacklist" }, }, }, - self_check = function(schema, plugin_t, dao, is_update) - local wl = type(plugin_t.whitelist) == "table" and plugin_t.whitelist or {} - local bl = type(plugin_t.blacklist) == "table" and plugin_t.blacklist or {} - - if #wl > 0 and #bl > 0 then - return false, Errors.schema "you cannot set both a whitelist and a blacklist" - elseif #wl == 0 and #bl == 0 then - return false, Errors.schema "you must set at least a whitelist or blacklist" - end - - return true - end } diff --git a/kong/plugins/jwt/schema.lua b/kong/plugins/jwt/schema.lua index bb48ae04720a..6da511900a35 100644 --- a/kong/plugins/jwt/schema.lua +++ b/kong/plugins/jwt/schema.lua @@ -1,54 +1,44 @@ -local utils = require "kong.tools.utils" -local Errors = require "kong.dao.errors" - -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then - return true - end - - return false, "the anonymous user must be empty or a valid uuid" -end - -local function check_positive(v) - if v < 0 then - return false, "should be 0 or greater" - end - - return true -end - return { - no_consumer = true, + name = "jwt", fields = { - uri_param_names = {type = "array", default = {"jwt"}}, - cookie_names = {type = "array", default = {}}, - key_claim_name = {type = "string", default = "iss"}, - secret_is_base64 = {type = "boolean", default = false}, - claims_to_verify = {type = "array", enum = {"exp", "nbf"}}, - anonymous = {type = "string", default = "", func = check_user}, - run_on_preflight = {type = "boolean", default = true}, - maximum_expiration = {type = "number", default = 0, func = check_positive}, + config = { + type = "record", + nullable = false, + fields = { + { uri_param_names = { + type = "set", + elements = { type = "string" }, + default = { "jwt" }, + }, }, + { cookie_names = { + type = "set", + elements = { type = "string" }, + default = {} + }, }, + { key_claim_name = { type = "string", default = "iss" }, }, + { secret_is_base64 = { type = "boolean", default = false }, }, + { claims_to_verify = { + type = "set", + elements = { + type = "string", + one_of = { "exp", "nbf" }, + }, }, }, + { anonymous = { type = "string", uuid = true, len_min = 0, default = "" }, }, + { run_on_preflight = { type = "boolean", default = true }, }, + { maximum_expiration = { + type = "number", + default = 0, + between = { 0, 31536000 }, + }, }, + }, + }, + }, + entity_checks = { + { conditional = { + if_field = "config.maximum_expiration", + if_match = { gt = 0 }, + then_field = "config.claims_to_verify", + then_match = { contains = "exp" }, + }, }, }, - self_check = function(schema, plugin_t, dao, is_update) - if plugin_t.maximum_expiration ~= nil - and plugin_t.maximum_expiration > 0 - then - local has_exp - - if plugin_t.claims_to_verify then - for index, value in ipairs(plugin_t.claims_to_verify) do - if value == "exp" then - has_exp = true - break - end - end - end - - if not has_exp then - return false, Errors.schema "claims_to_verify must contain 'exp' when specifying maximum_expiration" - end - end - - return true - end } diff --git a/kong/plugins/key-auth/schema.lua b/kong/plugins/key-auth/schema.lua index bf1a78a20c0c..f2b161e473cc 100644 --- a/kong/plugins/key-auth/schema.lua +++ b/kong/plugins/key-auth/schema.lua @@ -1,73 +1,25 @@ -local utils = require "kong.tools.utils" - - -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then - return true - end - - return false, "the anonymous user must be empty or a valid uuid" -end - - -local function check_keys(keys) - for _, key in ipairs(keys) do - local res, err = utils.validate_header_name(key, false) - - if not res then - return false, "'" .. key .. "' is illegal: " .. err - end - end - - return true -end - - -local function check_key(key) - local res, err = utils.validate_header_name(key, false) - - if not res then - return false, "'" .. key .. "' is illegal: " .. err - end - return true -end +local typedefs = require "kong.db.schema.typedefs" return { - no_consumer = true, + name = "key-auth", fields = { - key_names = { - new_type = { - type = "array", - required = true, - elements = { - type = "string", - custom_validator = check_key, - }, + { consumer = typedefs.no_consumer }, + { config = { + type = "record", nullable = false, - default = { "apikey" }, - }, - required = true, - type = "array", - default = { "apikey" }, - func = check_keys, - }, - hide_credentials = { - type = "boolean", - default = false, - }, - anonymous = { - type = "string", - default = "", - func = check_user, - }, - key_in_body = { - type = "boolean", - default = false, - }, - run_on_preflight = { - type = "boolean", - default = true, - }, - } + fields = { + { key_names = { + type = "array", + required = true, + elements = typedefs.header_name, + default = { "apikey" }, + }, }, + { hide_credentials = { type = "boolean", default = false }, }, + { anonymous = { type = "string", uuid = true, len_min = 0, default = "", required = true }, }, + { key_in_body = { type = "boolean", default = false }, }, + { run_on_preflight = { type = "boolean", default = true }, }, + }, + }, }, + }, } diff --git a/kong/plugins/ldap-auth/schema.lua b/kong/plugins/ldap-auth/schema.lua index f49d068309e6..fb3fa8c8cd0a 100644 --- a/kong/plugins/ldap-auth/schema.lua +++ b/kong/plugins/ldap-auth/schema.lua @@ -1,29 +1,29 @@ -local utils = require "kong.tools.utils" - -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then - return true - end - - return false, "the anonymous user must be empty or a valid uuid" -end +local typedefs = require "kong.db.schema.typedefs" -- If you add more configuration parameters, be sure to check if it needs to be added to cache key +-- Fields currently used for cache_key: ldap_host, ldap_port, base_dn, attribute, cache_ttl return { - no_consumer = true, + name = "ldap-auth", fields = { - ldap_host = {required = true, type = "string"}, -- used for cache key - ldap_port = {required = true, type = "number"}, -- used for cache key - start_tls = {required = true, type = "boolean", default = false}, - verify_ldap_host = {required = true, type = "boolean", default = false}, - base_dn = {required = true, type = "string"}, -- used for cache key - attribute = {required = true, type = "string"}, -- used for cache key - cache_ttl = {required = true, type = "number", default = 60}, -- used for cache key - hide_credentials = {type = "boolean", default = false}, - timeout = {type = "number", default = 10000}, - keepalive = {type = "number", default = 60000}, - anonymous = {type = "string", default = "", func = check_user}, - header_type = {type = "string", default = "ldap"}, - } + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { ldap_host = { type = "string", required = true }, }, + { ldap_port = { type = "integer", required = true }, }, + { start_tls = { type = "boolean", required = true, default = false }, }, + { verify_ldap_host = { type = "boolean", required = true, default = false }, }, + { base_dn = { type = "string", required = true }, }, + { attribute = { type = "string", required = true }, }, + { cache_ttl = { type = "number", required = true, default = 60 }, }, + { hide_credentials = { type = "boolean", default = false }, }, + { timeout = { type = "number", default = 10000 }, }, + { keepalive = { type = "number", default = 60000 }, }, + { anonymous = { type = "string", required = true, uuid = true, len_min = 0, default = "" }, }, + { header_type = { type = "string", default = "ldap" }, }, + }, + }, }, + }, } diff --git a/kong/plugins/loggly/schema.lua b/kong/plugins/loggly/schema.lua index fc069d58b45f..583c530dadae 100644 --- a/kong/plugins/loggly/schema.lua +++ b/kong/plugins/loggly/schema.lua @@ -1,15 +1,30 @@ -local ALLOWED_LEVELS = { "debug", "info", "notice", "warning", "err", "crit", "alert", "emerg" } +local severity = { + type = "string", + default = "info", + one_of = { "debug", "info", "notice", "warning", "err", "crit", "alert", "emerg" }, +} return { + name = "loggly", fields = { - host = { type = "string", default = "logs-01.loggly.com" }, - port = { type = "number", default = 514 }, - key = { required = true, type = "string"}, - tags = {type = "array", default = { "kong" }}, - log_level = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - successful_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - client_errors_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - server_errors_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - timeout = { type = "number", default = 10000 } - } + config = { + type = "record", + nullable = false, + fields = { + { host = { type = "string", default = "logs-01.loggly.com" }, }, + { port = { type = "integer", default = 514 }, }, + { key = { type = "string", required = true }, }, + { tags = { + type = "set", + default = { "kong" }, + elements = { type = "string" }, + }, }, + { log_level = severity }, + { successful_severity = severity }, + { client_errors_severity = severity }, + { server_errors_severity = severity }, + { timeout = { type = "number", default = 10000 }, }, + }, + }, + }, } diff --git a/kong/plugins/oauth2/schema.lua b/kong/plugins/oauth2/schema.lua index 7e35eba332a9..cc43bd55447b 100644 --- a/kong/plugins/oauth2/schema.lua +++ b/kong/plugins/oauth2/schema.lua @@ -1,44 +1,53 @@ -local utils = require "kong.tools.utils" -local Errors = require "kong.dao.errors" +local typedefs = require "kong.db.schema.typedefs" -local function check_user(anonymous) - if anonymous == "" or utils.is_valid_uuid(anonymous) then +local function validate_flows(config) + if config.enable_authorization_code + or config.enable_implicit_grant + or config.enable_client_credentials + or config.enable_password_grant + then return true end - return false, "the anonymous user must be empty or a valid uuid" -end - -local function check_mandatory_scope(v, t) - if v and not t.scopes then - return false, "To set a mandatory scope you also need to create available scopes" - end - return true + return nil, "at least one of these fields must be true: enable_authorization_code, enable_implicit_grant, enable_client_credentials, enable_password_grant" end return { - no_consumer = true, + name = "oauth2", fields = { - scopes = { required = false, type = "array" }, - mandatory_scope = { required = true, type = "boolean", default = false, func = check_mandatory_scope }, - provision_key = { required = false, unique = true, type = "string", default = utils.random_string }, - token_expiration = { required = true, type = "number", default = 7200 }, - enable_authorization_code = { required = true, type = "boolean", default = false }, - enable_implicit_grant = { required = true, type = "boolean", default = false }, - enable_client_credentials = { required = true, type = "boolean", default = false }, - enable_password_grant = { required = true, type = "boolean", default = false }, - hide_credentials = { type = "boolean", default = false }, - accept_http_if_already_terminated = { required = false, type = "boolean", default = false }, - anonymous = {type = "string", default = "", func = check_user}, - global_credentials = {type = "boolean", default = false}, - auth_header_name = {required = false, type = "string", default = "authorization"}, - refresh_token_ttl = {required = true, type = "number", default = 1209600} -- original hardcoded value - 14 days + { consumer = typedefs.no_consumer }, + { config = { + type = "record", + nullable = false, + fields = { + { scopes = { type = "array", elements = { type = "string" }, }, }, + { mandatory_scope = { type = "boolean", default = false, required = true }, }, + { provision_key = { type = "string", unique = true, auto = true, required = true }, }, + { token_expiration = { type = "number", default = 7200, required = true }, }, + { enable_authorization_code = { type = "boolean", default = false, required = true }, }, + { enable_implicit_grant = { type = "boolean", default = false, required = true }, }, + { enable_client_credentials = { type = "boolean", default = false, required = true }, }, + { enable_password_grant = { type = "boolean", default = false, required = true }, }, + { hide_credentials = { type = "boolean", default = false, required = true }, }, + { accept_http_if_already_terminated = { type = "boolean", default = false }, }, + { anonymous = { type = "string", uuid = true, len_min = 0, default = "" }, }, + { global_credentials = { type = "boolean", default = false }, }, + { auth_header_name = { type = "string", default = "authorization" }, }, + { refresh_token_ttl = { type = "number", default = 1209600, required = true }, }, + }, + custom_validator = validate_flows, + entity_checks = { + { conditional = { + if_field = "mandatory_scope", + if_match = { eq = true }, + then_field = "scopes", + then_match = { required = true }, + }, }, + }, + }, + }, }, - self_check = function(schema, plugin_t, dao, is_update) - if not plugin_t.enable_authorization_code and not plugin_t.enable_implicit_grant - and not plugin_t.enable_client_credentials and not plugin_t.enable_password_grant then - return false, Errors.schema "You need to enable at least one OAuth flow" - end - return true - end } + + + diff --git a/kong/plugins/rate-limiting/schema.lua b/kong/plugins/rate-limiting/schema.lua index 9cf5268ac18b..e67c17a04404 100644 --- a/kong/plugins/rate-limiting/schema.lua +++ b/kong/plugins/rate-limiting/schema.lua @@ -1,24 +1,59 @@ -local Errors = require "kong.dao.errors" +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 + + return true +end -local REDIS = "redis" return { + name = "rate-limiting", fields = { - second = { type = "number" }, - minute = { type = "number" }, - hour = { type = "number" }, - day = { type = "number" }, - month = { type = "number" }, - year = { type = "number" }, - limit_by = { type = "string", enum = {"consumer", "credential", "ip"}, default = "consumer" }, - policy = { type = "string", enum = {"local", "cluster", REDIS}, default = "cluster" }, - fault_tolerant = { type = "boolean", default = true }, - redis_host = { type = "string" }, - redis_port = { type = "number", default = 6379 }, - redis_password = { type = "string" }, - redis_timeout = { type = "number", default = 2000 }, - redis_database = { type = "number", default = 0 }, - hide_client_headers = { type = "boolean", default = false }, + config = { + type = "record", + nullable = false, + 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 }, }, + { limit_by = { + type = "string", + default = "consumer", + one_of = { "consumer", "credential", "ip" }, + }, }, + { policy = { + type = "string", + default = "cluster", + len_min = 0, + one_of = { "local", "cluster", "redis" }, + }, }, + { fault_tolerant = { type = "boolean", default = true }, }, + { redis_host = { type = "string" }, }, + { redis_port = { type = "integer", default = 6379 }, }, + { redis_password = { type = "string", len_min = 0 }, }, + { redis_timeout = { type = "number", default = 2000, }, }, + { redis_database = { type = "integer", default = 0 }, }, + { hide_client_headers = { type = "boolean", default = false }, }, + }, + custom_validator = validate_periods_order, + }, }, entity_checks = { { at_least_one_of = { "config.second", "config.minute", "config.hour", "config.day", "config.month", "config.year" } }, @@ -35,45 +70,4 @@ return { then_field = "config.redis_timeout", then_match = { required = true }, } }, }, - self_check = function(schema, plugin_t, dao, is_update) - local ordered_periods = { "second", "minute", "hour", "day", "month", "year"} - local has_value - local invalid_order - local invalid_value - - for i, v in ipairs(ordered_periods) do - if plugin_t[v] then - has_value = true - if plugin_t[v] <=0 then - invalid_value = "Value for " .. v .. " must be greater than zero" - else - for t = i+1, #ordered_periods do - if plugin_t[ordered_periods[t]] and plugin_t[ordered_periods[t]] < plugin_t[v] then - invalid_order = "The limit for " .. ordered_periods[t] .. " cannot be lower than the limit for " .. v - end - end - end - end - end - - if not has_value then - return false, Errors.schema "You need to set at least one limit: second, minute, hour, day, month, year" - elseif invalid_value then - return false, Errors.schema(invalid_value) - elseif invalid_order then - return false, Errors.schema(invalid_order) - end - - if plugin_t.policy == REDIS then - if not plugin_t.redis_host then - return false, Errors.schema "You need to specify a Redis host" - elseif not plugin_t.redis_port then - return false, Errors.schema "You need to specify a Redis port" - elseif not plugin_t.redis_timeout then - return false, Errors.schema "You need to specify a Redis timeout" - end - end - - return true - end } diff --git a/kong/plugins/request-size-limiting/schema.lua b/kong/plugins/request-size-limiting/schema.lua index affd13e97f35..0e7a108c0418 100644 --- a/kong/plugins/request-size-limiting/schema.lua +++ b/kong/plugins/request-size-limiting/schema.lua @@ -1,5 +1,12 @@ return { + name = "request-size-limiting", fields = { - allowed_payload_size = { default = 128, type = "number" } - } + config = { + type = "record", + nullable = false, + fields = { + { allowed_payload_size = { type = "integer", default = 128 }, }, + }, + }, + }, } diff --git a/kong/plugins/request-termination/schema.lua b/kong/plugins/request-termination/schema.lua index 351a6676a9a8..854787581b61 100644 --- a/kong/plugins/request-termination/schema.lua +++ b/kong/plugins/request-termination/schema.lua @@ -1,29 +1,37 @@ -local Errors = require "kong.dao.errors" +local is_present = function(v) + return type(v) == "string" and #v > 0 +end + return { + name = "request-termination", fields = { - status_code = { type = "number", default = 503 }, - message = { type = "string" }, - content_type = { type = "string" }, - body = { type = "string" }, + { config = { + type = "record", + nullable = false, + fields = { + { status_code = { + type = "integer", + default = 503, + between = { 100, 599 }, + }, }, + { message = { type = "string" }, }, + { content_type = { type = "string" }, }, + { body = { type = "string" }, }, + }, + custom_validator = function(config) + if is_present(config.message) + and(is_present(config.content_type) + or is_present(config.body)) then + return nil, "message cannot be used with content_type or body" + end + if is_present(config.content_type) + and not is_present(config.body) then + return nil, "content_type requires a body" + end + return true + end, + }, + }, }, - self_check = function(schema, plugin_t, dao, is_updating) - if plugin_t.status_code then - if plugin_t.status_code < 100 or plugin_t.status_code > 599 then - return false, Errors.schema("status_code must be between 100 .. 599") - end - end - - if plugin_t.message then - if plugin_t.content_type or plugin_t.body then - return false, Errors.schema("message cannot be used with content_type or body") - end - else - if plugin_t.content_type and not plugin_t.body then - return false, Errors.schema("content_type requires a body") - end - end - - return true - end } diff --git a/kong/plugins/request-transformer/schema.lua b/kong/plugins/request-transformer/schema.lua index 1388779e1042..046aa9009914 100644 --- a/kong/plugins/request-transformer/schema.lua +++ b/kong/plugins/request-transformer/schema.lua @@ -1,124 +1,56 @@ -local find = string.find --- entries must have colons to set the key and value apart -local function check_for_value(value) - for i, entry in ipairs(value) do - local ok = find(entry, ":") - if not ok then - return false, "key '" .. entry .. "' has no value" - end - end - return true -end +local typedefs = require "kong.db.schema.typedefs" + + +local strings_array = { + type = "array", + default = {}, + elements = { type = "string" }, +} + + +local strings_array_record = { + type = "record", + nullable = false, + fields = { + { body = strings_array }, + { headers = strings_array }, + { querystring = strings_array }, + }, +} + + +local colon_strings_array = { + type = "array", + default = {}, + elements = { type = "string", match = "^[^:]+:.*$" }, +} + + +local colon_strings_array_record = { + type = "record", + nullable = false, + fields = { + { body = colon_strings_array }, + { headers = colon_strings_array }, + { querystring = colon_strings_array }, + }, +} -local function check_method(value) - if not value then - return true - end - local method = value:upper() - local ngx_method = ngx["HTTP_" .. method] - if not ngx_method then - return false, method .. " is not supported" - end - return true -end return { + name = "request-transformer", fields = { - http_method = {type = "string", func = check_method}, - remove = { - new_type = { - type = "record", - fields = { - { body = { type = "array", elements = { type = "string" }, default = {} } }, - { headers = { type = "array", elements = { type = "string" }, default = {} } }, - { querystring = { type = "array", elements = { type = "string" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - body = {type = "array", default = {}}, -- does not need colons - headers = {type = "array", default = {}}, -- does not need colons - querystring = {type = "array", default = {}} -- does not need colons - } - } - }, - rename = { - new_type = { - type = "record", - fields = { - { body = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { querystring = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - body = {type = "array", default = {}}, - headers = {type = "array", default = {}}, - querystring = {type = "array", default = {}} - } + config = { + type = "record", + nullable = false, + fields = { + { http_method = typedefs.http_method }, + { remove = strings_array_record }, + { rename = colon_strings_array_record }, + { replace = colon_strings_array_record }, + { add = colon_strings_array_record }, + { append = colon_strings_array_record }, } }, - replace = { - new_type = { - type = "record", - fields = { - { body = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { querystring = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - body = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value}, - querystring = {type = "array", default = {}, func = check_for_value} - } - } - }, - add = { - new_type = { - type = "record", - fields = { - { body = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { querystring = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - body = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value}, - querystring = {type = "array", default = {}, func = check_for_value} - } - } - }, - append = { - new_type = { - type = "record", - fields = { - { body = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { querystring = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - body = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value}, - querystring = {type = "array", default = {}, func = check_for_value} - } - } - } } } diff --git a/kong/plugins/response-ratelimiting/schema.lua b/kong/plugins/response-ratelimiting/schema.lua index eb7252ba69fc..3ad2af4f55d5 100644 --- a/kong/plugins/response-ratelimiting/schema.lua +++ b/kong/plugins/response-ratelimiting/schema.lua @@ -1,84 +1,74 @@ -local Errors = require "kong.dao.errors" +local ORDERED_PERIODS = { "second", "minute", "hour", "day", "month", "year" } -local REDIS = "redis" -local function check_ordered_limits(limit_value) - local ordered_periods = { "second", "minute", "hour", "day", "month", "year"} - local has_value - local invalid_order - local invalid_value - - for i, v in ipairs(ordered_periods) do - if limit_value[v] then - has_value = true - if limit_value[v] <=0 then - invalid_value = "Value for " .. v .. " must be greater than zero" - else - for t = i+1, #ordered_periods do - if limit_value[ordered_periods[t]] and limit_value[ordered_periods[t]] < limit_value[v] then - invalid_order = "The limit for " .. ordered_periods[t] .. " cannot be lower than the limit for " .. v - end +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 - if not has_value then - return false, Errors.schema "You need to set at least one limit: second, minute, hour, day, month, year" - elseif invalid_value then - return false, Errors.schema(invalid_value) - elseif invalid_order then - return false, Errors.schema(invalid_order) - end - return true end + return { + name = "response-ratelimiting", fields = { - header_name = { type = "string", default = "x-kong-limit" }, - limit_by = { type = "string", enum = {"consumer", "credential", "ip"}, default = "consumer" }, - policy = { type = "string", enum = {"local", "cluster", REDIS}, default = "cluster" }, - fault_tolerant = { type = "boolean", default = true }, - redis_host = { type = "string" }, - redis_port = { type = "number", default = 6379 }, - redis_password = { type = "string" }, - redis_timeout = { type = "number", default = 2000 }, - redis_database = { type = "number", default = 0 }, - block_on_first_violation = { type = "boolean", default = false}, - limits = { type = "table", - schema = { - flexible = true, - fields = { - second = { type = "number" }, - minute = { type = "number" }, - hour = { type = "number" }, - day = { type = "number" }, - month = { type = "number" }, - year = { type = "number" } - } - }, - new_type = { - type = "map", - keys = { - type = "string", - }, - values = { - type = "record", - fields = { - { second = { type = "number" } }, - { minute = { type = "number" } }, - { hour = { type = "number" } }, - { day = { type = "number" } }, - { month = { type = "number" } }, - { year = { type = "number" } } - } + config = { + type = "record", + nullable = false, + fields = { + { header_name = { type = "string", default = "x-kong-limit" }, }, + { limit_by = { type = "string", + default = "consumer", + one_of = { "consumer", "credential", "ip" }, + }, }, + { policy = { type = "string", + default = "cluster", + one_of = { "local", "cluster", "redis" }, + }, }, + { fault_tolerant = { type = "boolean", default = true }, }, + { redis_host = { type = "string" }, }, + { redis_port = { type = "number", default = 6379 }, }, + { redis_password = { type = "string", len_min = 0 }, }, + { redis_timeout = { type = "number", default = 2000 }, }, + { redis_database = { type = "number", default = 0 }, }, + { block_on_first_violation = { type = "boolean", default = false}, }, + { hide_client_headers = { type = "boolean", default = false }, }, + { limits = { + type = "map", + required = true, + len_min = 1, + keys = { type = "string" }, + values = { + type = "record", + 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 }, + }, + }, + + }, }, - len_min = 1, - default = {}, }, }, - hide_client_headers = { type = "boolean", default = false }, }, entity_checks = { { conditional = { @@ -94,28 +84,4 @@ return { then_field = "config.redis_timeout", then_match = { required = true }, } }, }, - self_check = function(schema, plugin_t, dao, is_update) - if not plugin_t.limits or (not next(plugin_t.limits)) then - return false, Errors.schema "You need to set at least one limit name" - else - for k,v in pairs(plugin_t.limits) do - local ok, err = check_ordered_limits(v) - if not ok then - return false, err - end - end - end - - if plugin_t.policy == REDIS then - if not plugin_t.redis_host then - return false, Errors.schema "You need to specify a Redis host" - elseif not plugin_t.redis_port then - return false, Errors.schema "You need to specify a Redis port" - elseif not plugin_t.redis_timeout then - return false, Errors.schema "You need to specify a Redis timeout" - end - end - - return true - end } diff --git a/kong/plugins/response-transformer/schema.lua b/kong/plugins/response-transformer/schema.lua index 174dfe95c7e0..f0460fb9ae4e 100644 --- a/kong/plugins/response-transformer/schema.lua +++ b/kong/plugins/response-transformer/schema.lua @@ -1,85 +1,51 @@ -local find = string.find --- entries must have colons to set the key and value apart -local function check_for_value(value) - for i, entry in ipairs(value) do - local ok = find(entry, ":") - if not ok then - return false, "key '" .. entry .. "' has no value" - end - end - return true -end +local string_array = { + type = "array", + default = {}, + elements = { type = "string" }, +} + + +local colon_string_array = { + type = "array", + default = {}, + elements = { type = "string", match = "^[^:]+:.*$" }, +} + + + +local string_record = { + type = "record", + nullable = false, + fields = { + { json = string_array }, + { headers = string_array }, + }, +} + + +local colon_string_record = { + type = "record", + nullable = false, + fields = { + { json = colon_string_array }, + { headers = colon_string_array }, + }, +} + return { + name = "response-transformer", fields = { - -- add: Add a value (to response headers or response JSON body) only if the key does not already exist. - remove = { - new_type = { - type = "record", - fields = { - { json = { type = "array", elements = { type = "string" }, default = {} } }, - { headers = { type = "array", elements = { type = "string" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - json = {type = "array", default = {}}, -- does not need colons - headers = {type = "array", default = {}} -- does not need colons - } - } - }, - replace = { - new_type = { - type = "record", - fields = { - { json = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - json = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value} - } - } - }, - add = { - new_type = { - type = "record", - fields = { - { json = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, + config = { + type = "record", + nullable = false, + fields = { + { remove = string_record }, + { replace = colon_string_record }, + { add = colon_string_record }, + { append = colon_string_record }, }, - type = "table", - schema = { - fields = { - json = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value} - } - } }, - append = { - new_type = { - type = "record", - fields = { - { json = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - { headers = { type = "array", elements = { type = "string", match = "^[^:]+:.*$" }, default = {} } }, - }, - nullable = false, - }, - type = "table", - schema = { - fields = { - json = {type = "array", default = {}, func = check_for_value}, - headers = {type = "array", default = {}, func = check_for_value} - } - } - } - } + }, } + diff --git a/kong/plugins/statsd/schema.lua b/kong/plugins/statsd/schema.lua index d36ad3cc4269..fe0b7abd1b10 100644 --- a/kong/plugins/statsd/schema.lua +++ b/kong/plugins/statsd/schema.lua @@ -1,33 +1,21 @@ -local metrics = { - ["request_count"] = true, - ["latency"] = true, - ["request_size"] = true, - ["status_count"] = true, - ["response_size"] = true, - ["unique_users"] = true, - ["request_per_user"] = true, - ["upstream_latency"] = true, - ["kong_latency"] = true, - ["status_count_per_user"] = true, +local METRIC_NAMES = { + "kong_latency", "latency", "request_count", "request_per_user", + "request_size", "response_size", "status_count", "status_count_per_user", + "unique_users", "upstream_latency", } -local stat_types = { - ["gauge"] = true, - ["timer"] = true, - ["counter"] = true, - ["histogram"] = true, - ["meter"] = true, - ["set"] = true, +local STAT_TYPES = { + "counter", "gauge", "histogram", "meter", "set", "timer", } -local consumer_identifiers = { - ["consumer_id"] = true, - ["custom_id"] = true, - ["username"] = true, + +local CONSUMER_IDENTIFIERS = { + "consumer_id", "custom_id", "username", } -local default_metrics = { + +local DEFAULT_METRICS = { { name = "request_count", stat_type = "counter", @@ -78,97 +66,60 @@ local default_metrics = { } -local function check_schema(value) - for _, entry in ipairs(value) do - - if not entry.name or not entry.stat_type then - return false, "name and stat_type must be defined for all stats" - end - - if not metrics[entry.name] then - return false, "unrecognized metric name: " .. entry.name - end - - if not stat_types[entry.stat_type] then - return false, "unrecognized stat_type: " .. entry.stat_type - end - - if entry.name == "unique_users" and entry.stat_type ~= "set" then - return false, "unique_users metric only works with stat_type 'set'" - end - - if (entry.stat_type == "counter" or entry.stat_type == "gauge") - and ((not entry.sample_rate) or (entry.sample_rate - and type(entry.sample_rate) ~= "number") - or (entry.sample_rate and entry.sample_rate < 1)) then - - return false, "sample rate must be defined for counters and gauges." - end - - if (entry.name == "status_count_per_user" - or entry.name == "request_per_user" or entry.name == "unique_users") - and not entry.consumer_identifier then - - return false, "consumer_identifier must be defined for metric " .. - entry.name - end - - if (entry.name == "status_count_per_user" - or entry.name == "request_per_user" - or entry.name == "unique_users") - and entry.consumer_identifier - and not consumer_identifiers[entry.consumer_identifier] then - - return false, "invalid consumer_identifier for metric '" .. - entry.name .. - "'. Choices are consumer_id, custom_id, and username" - end - - if (entry.name == "status_count" - or entry.name == "status_count_per_user" - or entry.name == "request_per_user") - and entry.stat_type ~= "counter" then - - return false, entry.name .. " metric only works with stat_type 'counter'" - end - end - - return true -end - - return { + name = "statsd", fields = { - host = { - type = "string", - default = "localhost", - }, - port = { - type = "number", - default = 8125, - }, - metrics = { - type = "array", - default = default_metrics, - func = check_schema, - new_type = { - type = "array", - elements = { - type = "record", - fields = { - { name = { type = "string", required = true } }, - { stat_type = { type = "string", required = true } }, - { sample_rate = { type = "number" } }, - { consumer_identifier = { type = "string" } }, - } + config = { + type = "record", + nullable = false, + fields = { + { host = { type = "string", default = "localhost" }, }, + { port = { type = "integer", default = 8125 }, }, + { prefix = { type = "string", default = "kong" }, }, + { metrics = { + type = "array", + default = DEFAULT_METRICS, + elements = { + type = "record", + fields = { + { name = { type = "string", required = true, one_of = METRIC_NAMES }, }, + { stat_type = { type = "string", required = true, one_of = STAT_TYPES }, }, + { sample_rate = { type = "number", gt = 0 }, }, + { consumer_identifier = { type = "string", one_of = CONSUMER_IDENTIFIERS }, }, + }, + entity_checks = { + { conditional = { + if_field = "name", + if_match = { eq = "unique_users" }, + then_field = "stat_type", + then_match = { eq = "set" }, + }, }, + + { conditional = { + if_field = "stat_type", + if_match = { one_of = { "counter", "gauge" }, }, + then_field = "sample_rate", + then_match = { required = true }, + }, }, + + { conditional = { + if_field = "name", + if_match = { one_of = { "status_count_per_user", "request_per_user", "unique_users" }, }, + then_field = "consumer_identifier", + then_match = { required = true }, + }, }, + + { conditional = { + if_field = "name", + if_match = { one_of = { "status_count", "status_count_per_user", "request_per_user" }, }, + then_field = "stat_type", + then_match = { eq = "counter" }, + }, }, + }, + }, + }, }, - default = default_metrics, - } + }, }, - prefix = - { - type = "string", - default = "kong", - }, - } + }, } diff --git a/kong/plugins/syslog/schema.lua b/kong/plugins/syslog/schema.lua index 92dc60ce4ac9..4221ba4a481a 100644 --- a/kong/plugins/syslog/schema.lua +++ b/kong/plugins/syslog/schema.lua @@ -1,10 +1,21 @@ -local ALLOWED_LEVELS = { "debug", "info", "notice", "warning", "err", "crit", "alert", "emerg" } +local severity = { + type = "string", + default = "info", + one_of = { "debug", "info", "notice", "warning", "err", "crit", "alert", "emerg" }, +} return { + name = "syslog", fields = { - log_level = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - successful_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - client_errors_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - server_errors_severity = { type = "string", enum = ALLOWED_LEVELS, default = "info" }, - } + { config = { + type = "record", + nullable = false, + fields = { + { log_level = severity }, + { successful_severity = severity }, + { client_errors_severity = severity }, + { server_errors_severity = severity }, + }, }, }, + }, } + diff --git a/kong/plugins/tcp-log/schema.lua b/kong/plugins/tcp-log/schema.lua index 7c99f62ffe61..f3367555a414 100644 --- a/kong/plugins/tcp-log/schema.lua +++ b/kong/plugins/tcp-log/schema.lua @@ -1,10 +1,17 @@ return { + name = "tcp-log", fields = { - host = { required = true, type = "string" }, - port = { required = true, type = "number" }, - timeout = { default = 10000, type = "number" }, - keepalive = { default = 60000, type = "number" }, - tls = { default = false, type = "boolean" }, - tls_sni = { type = "string" }, + { config = { + type = "record", + nullable = false, + fields = { + { host = { type = "string", required = true }, }, + { port = { type = "integer", required = true }, }, + { timeout = { type = "number", default = 10000 }, }, + { keepalive = { type = "number", default = 60000 }, }, + { tls = { type = "boolean", default = false }, }, + { tls_sni = { type = "string" }, }, + }, + }, }, } } diff --git a/kong/plugins/udp-log/schema.lua b/kong/plugins/udp-log/schema.lua index c24a7800b13f..c01c127158e8 100644 --- a/kong/plugins/udp-log/schema.lua +++ b/kong/plugins/udp-log/schema.lua @@ -1,7 +1,13 @@ return { + name = "udp-log", fields = { - host = { required = true, type = "string" }, - port = { required = true, type = "number" }, - timeout = { default = 10000, type = "number" } - } + { config = { + type = "record", + nullable = false, + fields = { + { host = { type = "string", required = true }, }, + { port = { type = "integer", required = true }, }, + { timeout = { type = "number", default = 10000 }, }, + }, }, }, + }, } diff --git a/spec/01-unit/000-new-dao/01-schema/06-plugins_spec.lua b/spec/01-unit/000-new-dao/01-schema/06-plugins_spec.lua index 719c6572a7c2..008bda3ade00 100644 --- a/spec/01-unit/000-new-dao/01-schema/06-plugins_spec.lua +++ b/spec/01-unit/000-new-dao/01-schema/06-plugins_spec.lua @@ -118,14 +118,13 @@ describe("plugins", function() assert.same({ key_names = { "apikey" }, hide_credentials = false, - anonymous = "", + anonymous = ngx.null, key_in_body = false, run_on_preflight = true, }, plugin.config) end) it("should be valid if no value is specified for a subfield and if the config schema has default as empty array", function() - it("#should be valid if no value is specified for a subfield and if the config schema has default as empty array", function() -- Insert response-transformer, whose default config has no default values, and should be empty local plugin = { name = "response-transformer", diff --git a/spec/fixtures/custom_plugins/kong/plugins/dummy/schema.lua b/spec/fixtures/custom_plugins/kong/plugins/dummy/schema.lua index 4985df3482fe..56d7013b667c 100644 --- a/spec/fixtures/custom_plugins/kong/plugins/dummy/schema.lua +++ b/spec/fixtures/custom_plugins/kong/plugins/dummy/schema.lua @@ -1,7 +1,13 @@ return { + name = "dummy", fields = { - resp_header_value = { type = "string", default = "1" }, - append_body = { type = "string" }, - resp_code = { type = "number" }, - } + { config = { + type = "record", + nullable = false, + fields = { + { resp_header_value = { type = "string", default = "1" }, }, + { append_body = { type = "string" }, }, + { resp_code = { type = "number" }, }, + }, }, }, + }, } diff --git a/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua b/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua index 7060f58f9d27..2be297f3867b 100644 --- a/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua +++ b/spec/fixtures/custom_plugins/kong/plugins/rewriter/schema.lua @@ -1,9 +1,13 @@ return { + name = "rewriter", fields = { - value = { type = "string" }, - extra = { - type = "string", - default = "extra", - } + { config = { + type = "record", + nullable = false, + fields = { + { value = { type = "string" }, }, + { extra = { type = "string", default = "extra" }, }, + }, + }, }, } }