308 lines
12 KiB
Lua
308 lines
12 KiB
Lua
local typedefs = require "kong.db.schema.typedefs"
|
|
local reserved_words = require "kong.plugins.acme.reserved_words"
|
|
local redis_schema = require "kong.tools.redis.schema"
|
|
local shallow_copy = require("kong.tools.table").shallow_copy
|
|
|
|
local CERT_TYPES = { "rsa", "ecc" }
|
|
|
|
local RSA_KEY_SIZES = { 2048, 3072, 4096 }
|
|
|
|
local STORAGE_TYPES = { "kong", "shm", "redis", "consul", "vault" }
|
|
|
|
local function validate_namespace(namespace)
|
|
if namespace ~= "" then
|
|
for _, v in pairs(reserved_words) do
|
|
if namespace:sub(1, #v) == v then
|
|
return nil, "namespace can't be prefixed with reserved word: " .. v
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local SHM_STORAGE_SCHEMA = {
|
|
{
|
|
shm_name = {
|
|
description = "Name of shared memory zone used for Kong API gateway storage",
|
|
type = "string",
|
|
default = "kong",
|
|
custom_validator = function(d) return ngx.shared[d] end,
|
|
},
|
|
},
|
|
}
|
|
|
|
-- must be a table per schema definition
|
|
local KONG_STORAGE_SCHEMA = {
|
|
}
|
|
|
|
local LEGACY_SCHEMA_TRANSLATIONS = {
|
|
{ auth = {
|
|
type = "string",
|
|
len_min = 0,
|
|
deprecation = {
|
|
replaced_with = { { path = { 'password' } } },
|
|
message = "acme: config.storage_config.redis.auth is deprecated, please use config.storage_config.redis.password instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { password = value }
|
|
end
|
|
}},
|
|
{ ssl_server_name = {
|
|
type = "string",
|
|
deprecation = {
|
|
replaced_with = { { path = { 'server_name' } } },
|
|
message = "acme: config.storage_config.redis.ssl_server_name is deprecated, please use config.storage_config.redis.server_name instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { server_name = value }
|
|
end
|
|
}},
|
|
{ namespace = {
|
|
type = "string",
|
|
len_min = 0,
|
|
deprecation = {
|
|
replaced_with = { { path = { 'extra_options', 'namespace' } } },
|
|
message = "acme: config.storage_config.redis.namespace is deprecated, please use config.storage_config.redis.extra_options.namespace instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { extra_options = { namespace = value } }
|
|
end
|
|
}},
|
|
{ scan_count = {
|
|
type = "integer",
|
|
deprecation = {
|
|
replaced_with = { { path = { 'extra_options', 'scan_count' } } },
|
|
message = "acme: config.storage_config.redis.scan_count is deprecated, please use config.storage_config.redis.extra_options.scan_count instead",
|
|
removal_in_version = "4.0", },
|
|
func = function(value)
|
|
return { extra_options = { scan_count = value } }
|
|
end
|
|
}},
|
|
}
|
|
|
|
local REDIS_STORAGE_SCHEMA = shallow_copy(redis_schema.config_schema.fields)
|
|
table.insert(REDIS_STORAGE_SCHEMA, { extra_options = {
|
|
description = "Custom ACME Redis options",
|
|
type = "record",
|
|
fields = {
|
|
{
|
|
namespace = {
|
|
type = "string",
|
|
description = "A namespace to prepend to all keys stored in Redis.",
|
|
required = true,
|
|
default = "",
|
|
len_min = 0,
|
|
custom_validator = validate_namespace
|
|
}
|
|
},
|
|
{ scan_count = { type = "number", required = false, default = 10, description = "The number of keys to return in Redis SCAN calls." } },
|
|
}
|
|
} })
|
|
|
|
local CONSUL_STORAGE_SCHEMA = {
|
|
{ https = { type = "boolean", default = false, description = "Boolean representation of https."}, },
|
|
{ host = typedefs.host},
|
|
{ port = typedefs.port},
|
|
{ kv_path = { type = "string", description = "KV prefix path."}, },
|
|
{ timeout = { type = "number", description = "Timeout in milliseconds."}, },
|
|
{ token = { type = "string", referenceable = true, description = "Consul ACL token."}, },
|
|
}
|
|
|
|
local VAULT_STORAGE_SCHEMA = {
|
|
{ https = { type = "boolean", default = false, description = "Boolean representation of https." }, },
|
|
{ host = typedefs.host, },
|
|
{ port = typedefs.port, },
|
|
{ kv_path = { type = "string", description = "KV prefix path." }, },
|
|
{ timeout = { type = "number", description = "Timeout in milliseconds."}, },
|
|
{ token = { type = "string", referenceable = true, description = "Consul ACL token." }, },
|
|
{ tls_verify = { type = "boolean", default = true, description = "Turn on TLS verification." }, },
|
|
{ tls_server_name = { type = "string", description = "SNI used in request, default to host if omitted." }, },
|
|
{ auth_method = { type = "string", default = "token", one_of = { "token", "kubernetes" }, description = "Auth Method, default to token, can be 'token' or 'kubernetes'." } },
|
|
{ auth_path = { type = "string", description = "Vault's authentication path to use." }, },
|
|
{ auth_role = { type = "string", description = "The role to try and assign." }, },
|
|
{ jwt_path = { type = "string", description = "The path to the JWT." }, },
|
|
}
|
|
|
|
local ACCOUNT_KEY_SCHEMA = {
|
|
{ key_id = { type = "string", required = true, description = "The Key ID." } },
|
|
{ key_set = { type = "string", description = "The ID of the key set to associate the Key ID with." } }
|
|
}
|
|
|
|
local schema = {
|
|
name = "acme",
|
|
fields = {
|
|
-- global plugin only
|
|
{ consumer = typedefs.no_consumer },
|
|
{ service = typedefs.no_service },
|
|
{ route = typedefs.no_route },
|
|
{ protocols = typedefs.protocols_http },
|
|
{
|
|
config = {
|
|
type = "record",
|
|
fields = {
|
|
{
|
|
account_email = {
|
|
description = "The account identifier. Can be reused in a different plugin instance.",
|
|
type = "string",
|
|
-- very loose validation for basic sanity test
|
|
match = "%w*%p*@+%w*%.?%w*",
|
|
required = true,
|
|
encrypted = true, -- Kong Enterprise-exclusive feature, does nothing in Kong CE
|
|
referenceable = true,
|
|
},
|
|
},
|
|
{
|
|
account_key = {
|
|
description = "The private key associated with the account.",
|
|
type = "record",
|
|
required = false,
|
|
fields = ACCOUNT_KEY_SCHEMA,
|
|
},
|
|
},
|
|
{
|
|
api_uri = typedefs.url({ default = "https://acme-v02.api.letsencrypt.org/directory" }),
|
|
},
|
|
{
|
|
tos_accepted = {
|
|
type = "boolean",
|
|
description = "If you are using Let's Encrypt, you must set this to `true` to agree the terms of service.",
|
|
default = false,
|
|
},
|
|
},
|
|
{
|
|
eab_kid = {
|
|
description = "External account binding (EAB) key id. You usually don't need to set this unless it is explicitly required by the CA.",
|
|
type = "string",
|
|
encrypted = true, -- Kong Enterprise-exclusive feature, does nothing in Kong CE
|
|
referenceable = true,
|
|
},
|
|
},
|
|
{
|
|
eab_hmac_key = {
|
|
description = "External account binding (EAB) base64-encoded URL string of the HMAC key. You usually don't need to set this unless it is explicitly required by the CA.",
|
|
type = "string",
|
|
encrypted = true, -- Kong Enterprise-exclusive feature, does nothing in Kong CE
|
|
referenceable = true,
|
|
},
|
|
},
|
|
-- Kong doesn't support multiple certificate chains yet
|
|
{
|
|
cert_type = {
|
|
description = "The certificate type to create. The possible values are `rsa` for RSA certificate or `ecc` for EC certificate.",
|
|
type = "string",
|
|
default = 'rsa',
|
|
one_of = CERT_TYPES,
|
|
},
|
|
},
|
|
{
|
|
rsa_key_size = {
|
|
description = "RSA private key size for the certificate. The possible values are 2048, 3072, or 4096.",
|
|
type = "number",
|
|
default = 4096,
|
|
one_of = RSA_KEY_SIZES,
|
|
},
|
|
},
|
|
{
|
|
renew_threshold_days = {
|
|
description = "Days remaining to renew the certificate before it expires.",
|
|
type = "number",
|
|
default = 14,
|
|
},
|
|
},
|
|
{ domains = typedefs.hosts },
|
|
{
|
|
allow_any_domain = {
|
|
description = "If set to `true`, the plugin allows all domains and ignores any values in the `domains` list.",
|
|
type = "boolean",
|
|
default = false,
|
|
},
|
|
},
|
|
{
|
|
fail_backoff_minutes = {
|
|
description = "Minutes to wait for each domain that fails to create a certificate. This applies to both a\nnew certificate and a renewal certificate.",
|
|
type = "number",
|
|
default = 5,
|
|
},
|
|
},
|
|
{
|
|
storage = {
|
|
description = "The backend storage type to use. In DB-less mode and Konnect, `kong` storage is unavailable. In hybrid mode and Konnect, `shm` storage is unavailable. `shm` storage does not persist during Kong restarts and does not work for Kong running on different machines, so consider using one of `kong`, `redis`, `consul`, or `vault` in production.",
|
|
type = "string",
|
|
default = "shm",
|
|
one_of = STORAGE_TYPES,
|
|
},
|
|
},
|
|
{
|
|
storage_config = {
|
|
type = "record",
|
|
fields = {
|
|
{ shm = { type = "record", fields = SHM_STORAGE_SCHEMA, } },
|
|
{ kong = { type = "record", fields = KONG_STORAGE_SCHEMA, } },
|
|
{ redis = { type = "record", fields = REDIS_STORAGE_SCHEMA, shorthand_fields = LEGACY_SCHEMA_TRANSLATIONS } },
|
|
{ consul = { type = "record", fields = CONSUL_STORAGE_SCHEMA, } },
|
|
{ vault = { type = "record", fields = VAULT_STORAGE_SCHEMA, } },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
preferred_chain = {
|
|
description = "A string value that specifies the preferred certificate chain to use when generating certificates.",
|
|
type = "string",
|
|
},
|
|
},
|
|
{
|
|
enable_ipv4_common_name = {
|
|
description = "A boolean value that controls whether to include the IPv4 address in the common name field of generated certificates.",
|
|
type = "boolean",
|
|
default = true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
entity_checks = {
|
|
{
|
|
conditional = {
|
|
if_field = "config.api_uri",
|
|
if_match = {
|
|
one_of = {
|
|
"https://acme-v02.api.letsencrypt.org",
|
|
"https://acme-staging-v02.api.letsencrypt.org",
|
|
}
|
|
},
|
|
then_field = "config.tos_accepted",
|
|
then_match = { eq = true },
|
|
then_err = "terms of service must be accepted, see https://letsencrypt.org/repository/",
|
|
}
|
|
},
|
|
{ conditional = {
|
|
if_field = "config.storage", if_match = { eq = "redis" },
|
|
then_field = "config.storage_config.redis.host", then_match = { required = true },
|
|
} },
|
|
{ conditional = {
|
|
if_field = "config.storage", if_match = { eq = "redis" },
|
|
then_field = "config.storage_config.redis.port", then_match = { required = true },
|
|
} },
|
|
{
|
|
custom_entity_check = {
|
|
field_sources = { "config.storage", },
|
|
fn = function(entity)
|
|
local field = entity.config.storage
|
|
if _G.kong and kong.configuration.database == "off" and
|
|
kong.configuration.role ~= "data_plane" and field == "kong" then
|
|
return nil, "\"kong\" storage can't be used with dbless mode"
|
|
end
|
|
if _G.kong and kong.configuration.role == "control_plane" and field == "shm" then
|
|
return nil, "\"shm\" storage can't be used in Hybrid mode"
|
|
end
|
|
return true
|
|
end
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
return schema
|