From 1c4bfb3ebb0d714edb0b00c74b58a819000f5921 Mon Sep 17 00:00:00 2001 From: Zhefeng C <38037704+catbro666@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:52:42 +0800 Subject: [PATCH] fix(ca_certificates): invalidate ca store caches when a ca cert is updated and prevent ca_certificates that are still being referenced by other entities from being deleted (#11789) * fix(ca_certificates): invalidate ca store caches when a ca cert is updated and prevent ca_certificates that are still being referenced by other entities from being deleted. Fix [FTI-2060](https://konghq.atlassian.net/browse/FTI-2060) * apply comments * change plugin tables from maps to arrays * fix plugin_name double check * remove `search_fields` for now as it is EE-only * do the iteration and filtering in dao by adding `select_by_ca_certificate` * auto-detect the entities and plugins that reference ca certificates to make it more generic. create a custom ca_certificates dao and put the check_ca_reference logic into the `:delete()` method instead of a custom API route * update the schema of ca_certificates * fix: fields in schema is an array and cert_pk is a table * add services:select_by_ca_certificate() tests * fix lint * add custom plugin "reference-ca-cert" and plugins:select_by_ca_certificate() tests * add ca_certificates:delete() tests * Apply suggestions from code review Co-authored-by: Michael Martin * fix typo * remove plugins.lua and services.lua for `off` as they're not currently being used --------- Co-authored-by: Michael Martin --- .../kong/ca_certificates_reference_check.yml | 3 + kong-3.6.0-0.rockspec | 4 + kong/api/endpoints.lua | 1 + kong/db/dao/ca_certificates.lua | 55 ++++ kong/db/dao/plugins.lua | 18 ++ kong/db/dao/services.lua | 16 + kong/db/errors.lua | 11 + kong/db/schema/entities/ca_certificates.lua | 1 + kong/db/schema/entities/services.lua | 1 + kong/db/strategies/postgres/plugins.lua | 39 +++ kong/db/strategies/postgres/services.lua | 20 ++ kong/runloop/certificate.lua | 99 ++++++ kong/runloop/events.lua | 53 ++++ spec/02-integration/03-db/03-plugins_spec.lua | 296 +++++++++++++++++- .../02-integration/03-db/21-services_spec.lua | 215 +++++++++++++ .../03-db/22-ca_certificates_spec.lua | 145 +++++++++ .../16-ca_certificates_routes_spec.lua | 27 ++ .../05-proxy/18-upstream_tls_spec.lua | 178 ++++++++++- .../plugins/reference-ca-cert/handler.lua | 6 + .../kong/plugins/reference-ca-cert/schema.lua | 15 + 20 files changed, 1189 insertions(+), 14 deletions(-) create mode 100644 changelog/unreleased/kong/ca_certificates_reference_check.yml create mode 100644 kong/db/dao/ca_certificates.lua create mode 100644 kong/db/dao/services.lua create mode 100644 kong/db/strategies/postgres/plugins.lua create mode 100644 kong/db/strategies/postgres/services.lua create mode 100644 spec/02-integration/03-db/21-services_spec.lua create mode 100644 spec/02-integration/03-db/22-ca_certificates_spec.lua create mode 100644 spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/handler.lua create mode 100644 spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/schema.lua diff --git a/changelog/unreleased/kong/ca_certificates_reference_check.yml b/changelog/unreleased/kong/ca_certificates_reference_check.yml new file mode 100644 index 00000000000..3ac9d8a3aab --- /dev/null +++ b/changelog/unreleased/kong/ca_certificates_reference_check.yml @@ -0,0 +1,3 @@ +message: prevent ca to be deleted when it's still referenced by other entities and invalidate the related ca store caches when a ca cert is updated. +type: bugfix +scope: Core diff --git a/kong-3.6.0-0.rockspec b/kong-3.6.0-0.rockspec index f08b00d014e..1617e7ff99e 100644 --- a/kong-3.6.0-0.rockspec +++ b/kong-3.6.0-0.rockspec @@ -212,6 +212,8 @@ build = { ["kong.db.dao.tags"] = "kong/db/dao/tags.lua", ["kong.db.dao.vaults"] = "kong/db/dao/vaults.lua", ["kong.db.dao.workspaces"] = "kong/db/dao/workspaces.lua", + ["kong.db.dao.services"] = "kong/db/dao/services.lua", + ["kong.db.dao.ca_certificates"] = "kong/db/dao/ca_certificates.lua", ["kong.db.declarative"] = "kong/db/declarative/init.lua", ["kong.db.declarative.marshaller"] = "kong/db/declarative/marshaller.lua", ["kong.db.declarative.export"] = "kong/db/declarative/export.lua", @@ -251,6 +253,8 @@ build = { ["kong.db.strategies.postgres"] = "kong/db/strategies/postgres/init.lua", ["kong.db.strategies.postgres.connector"] = "kong/db/strategies/postgres/connector.lua", ["kong.db.strategies.postgres.tags"] = "kong/db/strategies/postgres/tags.lua", + ["kong.db.strategies.postgres.services"] = "kong/db/strategies/postgres/services.lua", + ["kong.db.strategies.postgres.plugins"] = "kong/db/strategies/postgres/plugins.lua", ["kong.db.strategies.off"] = "kong/db/strategies/off/init.lua", ["kong.db.strategies.off.connector"] = "kong/db/strategies/off/connector.lua", ["kong.db.strategies.off.tags"] = "kong/db/strategies/off/tags.lua", diff --git a/kong/api/endpoints.lua b/kong/api/endpoints.lua index 0ca7dbe8ccc..eb995a357b7 100644 --- a/kong/api/endpoints.lua +++ b/kong/api/endpoints.lua @@ -35,6 +35,7 @@ local ERRORS_HTTP_CODES = { [Errors.codes.INVALID_OPTIONS] = 400, [Errors.codes.OPERATION_UNSUPPORTED] = 405, [Errors.codes.FOREIGN_KEYS_UNRESOLVED] = 400, + [Errors.codes.REFERENCED_BY_OTHERS] = 400, } local TAGS_AND_REGEX diff --git a/kong/db/dao/ca_certificates.lua b/kong/db/dao/ca_certificates.lua new file mode 100644 index 00000000000..4720b3881b3 --- /dev/null +++ b/kong/db/dao/ca_certificates.lua @@ -0,0 +1,55 @@ +local certificate = require "kong.runloop.certificate" +local fmt = string.format + +local Ca_certificates = {} + +-- returns the first encountered entity element that is referencing the ca cert +-- otherwise, returns nil, err +function Ca_certificates:check_ca_reference(ca_id) + for _, entity in ipairs(certificate.get_ca_certificate_reference_entities()) do + local elements, err = self.db[entity]:select_by_ca_certificate(ca_id, 1) + if err then + local msg = fmt("failed to select %s by ca certificate %s: %s", entity, ca_id, err) + return nil, msg + end + + if type(elements) == "table" and #elements > 0 then + return entity, elements[1] + end + end + + local reference_plugins = certificate.get_ca_certificate_reference_plugins() + if reference_plugins and next(reference_plugins) then + local plugins, err = self.db.plugins:select_by_ca_certificate(ca_id, 1, reference_plugins) + if err then + local msg = fmt("failed to select plugins by ca_certificate %s: %s", ca_id, err) + return nil, msg + end + + if type(plugins) == "table" and #plugins > 0 then + return "plugins", plugins[1] + end + end + + return nil, nil +end + +-- Overrides the default delete function to check the ca reference before deleting +function Ca_certificates:delete(cert_pk, options) + local entity, element_or_err = self:check_ca_reference(cert_pk.id) + if entity then + local msg = fmt("ca certificate %s is still referenced by %s (id = %s)", + cert_pk.id, entity, element_or_err.id) + local err_t = self.errors:referenced_by_others(msg) + return nil, tostring(err_t), err_t + + elseif element_or_err then + local err_t = self.errors:database_error(element_or_err) + return nil, tostring(err_t), err_t + end + + return self.super.delete(self, cert_pk, options) +end + + +return Ca_certificates diff --git a/kong/db/dao/plugins.lua b/kong/db/dao/plugins.lua index 8790de32c2c..58521cc07f8 100644 --- a/kong/db/dao/plugins.lua +++ b/kong/db/dao/plugins.lua @@ -371,5 +371,23 @@ function Plugins:get_handlers() return list end +-- @ca_id: the id of ca certificate to be searched +-- @limit: the maximum number of entities to return (must >= 0) +-- @plugin_names: the plugin names to filter the entities (must be of type table, string or nil) +-- @return an array of the plugin entity +function Plugins:select_by_ca_certificate(ca_id, limit, plugin_names) + local param_type = type(plugin_names) + if param_type ~= "table" and param_type ~= "string" and param_type ~= "nil" then + return nil, "parameter `plugin_names` must be of type table, string, or nil" + end + + local plugins, err = self.strategy:select_by_ca_certificate(ca_id, limit, plugin_names) + if err then + return nil, err + end + + return self:rows_to_entities(plugins), nil +end + return Plugins diff --git a/kong/db/dao/services.lua b/kong/db/dao/services.lua new file mode 100644 index 00000000000..d79c1618e12 --- /dev/null +++ b/kong/db/dao/services.lua @@ -0,0 +1,16 @@ + +local Services = {} + +-- @ca_id: the id of ca certificate to be searched +-- @limit: the maximum number of entities to return (must >= 0) +-- @return an array of the service entity +function Services:select_by_ca_certificate(ca_id, limit) + local services, err = self.strategy:select_by_ca_certificate(ca_id, limit) + if err then + return nil, err + end + + return self:rows_to_entities(services), nil +end + +return Services diff --git a/kong/db/errors.lua b/kong/db/errors.lua index e5c01f3473f..5a43911741a 100644 --- a/kong/db/errors.lua +++ b/kong/db/errors.lua @@ -52,6 +52,7 @@ local ERRORS = { INVALID_FOREIGN_KEY = 16, -- foreign key is valid for matching a row INVALID_WORKSPACE = 17, -- strategy reports a workspace error INVALID_UNIQUE_GLOBAL = 18, -- unique field value is invalid for global query + REFERENCED_BY_OTHERS = 19, -- still referenced by other entities } @@ -77,6 +78,7 @@ local ERRORS_NAMES = { [ERRORS.INVALID_FOREIGN_KEY] = "invalid foreign key", [ERRORS.INVALID_WORKSPACE] = "invalid workspace", [ERRORS.INVALID_UNIQUE_GLOBAL] = "invalid global query", + [ERRORS.REFERENCED_BY_OTHERS] = "referenced by others", } @@ -517,6 +519,15 @@ function _M:invalid_unique_global(name) end +function _M:referenced_by_others(err) + if type(err) ~= "string" then + error("err must be a string", 2) + end + + return new_err_t(self, ERRORS.REFERENCED_BY_OTHERS, err) +end + + local flatten_errors do local function singular(noun) diff --git a/kong/db/schema/entities/ca_certificates.lua b/kong/db/schema/entities/ca_certificates.lua index f87cd35722b..212c79dd3cc 100644 --- a/kong/db/schema/entities/ca_certificates.lua +++ b/kong/db/schema/entities/ca_certificates.lua @@ -11,6 +11,7 @@ local CERT_TAG_LEN = #CERT_TAG return { name = "ca_certificates", primary_key = { "id" }, + dao = "kong.db.dao.ca_certificates", fields = { { id = typedefs.uuid, }, diff --git a/kong/db/schema/entities/services.lua b/kong/db/schema/entities/services.lua index 030eb90c438..cf2954a3677 100644 --- a/kong/db/schema/entities/services.lua +++ b/kong/db/schema/entities/services.lua @@ -23,6 +23,7 @@ return { primary_key = { "id" }, workspaceable = true, endpoint_key = "name", + dao = "kong.db.dao.services", fields = { { id = typedefs.uuid, }, diff --git a/kong/db/strategies/postgres/plugins.lua b/kong/db/strategies/postgres/plugins.lua new file mode 100644 index 00000000000..6a08a4a825f --- /dev/null +++ b/kong/db/strategies/postgres/plugins.lua @@ -0,0 +1,39 @@ +local kong = kong +local fmt = string.format +local tb_insert = table.insert +local tb_concat = table.concat + +local Plugins = {} + +function Plugins:select_by_ca_certificate(ca_id, limit, plugin_names) + local connector = kong.db.connector + local escape_literal = connector.escape_literal + local limit_condition = "" + if limit then + limit_condition = "LIMIT " .. escape_literal(connector, limit) + end + + local name_condition = "" + local escaped_names = {} + if type(plugin_names) == "string" then + tb_insert(escaped_names, "name = " .. escape_literal(connector, plugin_names)) + elseif type(plugin_names) == "table" then + for name, _ in pairs(plugin_names) do + tb_insert(escaped_names, "name = " .. escape_literal(connector, name)) + end + end + + if #escaped_names > 0 then + name_condition = "AND (" .. tb_concat(escaped_names, " OR ") .. ")" + end + + local qs = fmt( + "SELECT * FROM plugins WHERE config->'ca_certificates' ? %s %s %s;", + escape_literal(connector, ca_id), + name_condition, + limit_condition) + + return connector:query(qs) +end + +return Plugins diff --git a/kong/db/strategies/postgres/services.lua b/kong/db/strategies/postgres/services.lua new file mode 100644 index 00000000000..02393a4249e --- /dev/null +++ b/kong/db/strategies/postgres/services.lua @@ -0,0 +1,20 @@ +local kong = kong +local fmt = string.format + +local Services = {} + +function Services:select_by_ca_certificate(ca_id, limit) + local limit_condition = "" + if limit then + limit_condition = "LIMIT " .. kong.db.connector:escape_literal(limit) + end + + local qs = fmt( + "SELECT * FROM services WHERE %s = ANY(ca_certificates) %s;", + kong.db.connector:escape_literal(ca_id), + limit_condition) + + return kong.db.connector:query(qs) +end + +return Services diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 53da6b3d8d3..f52f338ac68 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -2,6 +2,9 @@ local ngx_ssl = require "ngx.ssl" local pl_utils = require "pl.utils" local mlcache = require "kong.resty.mlcache" local new_tab = require "table.new" +local constants = require "kong.constants" +local utils = require "kong.tools.utils" +local plugin_servers = require "kong.runloop.plugin_servers" local openssl_x509_store = require "resty.openssl.x509.store" local openssl_x509 = require "resty.openssl.x509" @@ -19,6 +22,7 @@ local set_cert = ngx_ssl.set_cert local set_priv_key = ngx_ssl.set_priv_key local tb_concat = table.concat local tb_sort = table.sort +local tb_insert = table.insert local kong = kong local type = type local error = error @@ -371,6 +375,97 @@ local function get_ca_certificate_store(ca_ids) end +local function get_ca_certificate_store_for_plugin(ca_ids) + return kong.cache:get(ca_ids_cache_key(ca_ids), + get_ca_store_opts, fetch_ca_certificates, + ca_ids) +end + + +-- here we assume the field name is always `ca_certificates` +local get_ca_certificate_reference_entities +do + local function is_entity_referencing_ca_certificates(name) + local entity_schema = require("kong.db.schema.entities." .. name) + for _, field in ipairs(entity_schema.fields) do + if field.ca_certificates then + return true + end + end + + return false + end + + -- ordinary entities that reference ca certificates + -- For example: services + local CA_CERT_REFERENCE_ENTITIES + get_ca_certificate_reference_entities = function() + if not CA_CERT_REFERENCE_ENTITIES then + CA_CERT_REFERENCE_ENTITIES = {} + for _, entity_name in ipairs(constants.CORE_ENTITIES) do + local res = is_entity_referencing_ca_certificates(entity_name) + if res then + tb_insert(CA_CERT_REFERENCE_ENTITIES, entity_name) + end + end + end + + return CA_CERT_REFERENCE_ENTITIES + end +end + + +-- here we assume the field name is always `ca_certificates` +local get_ca_certificate_reference_plugins +do + local function is_plugin_referencing_ca_certificates(name) + local plugin_schema = "kong.plugins." .. name .. ".schema" + local ok, schema = utils.load_module_if_exists(plugin_schema) + if not ok then + ok, schema = plugin_servers.load_schema(name) + end + + if not ok then + return nil, "no configuration schema found for plugin: " .. name + end + + for _, field in ipairs(schema.fields) do + if field.config then + for _, field in ipairs(field.config.fields) do + if field.ca_certificates then + return true + end + end + end + end + + return false + end + + -- loaded plugins that reference ca certificates + -- For example: mtls-auth + local CA_CERT_REFERENCE_PLUGINS + get_ca_certificate_reference_plugins = function() + if not CA_CERT_REFERENCE_PLUGINS then + CA_CERT_REFERENCE_PLUGINS = {} + local loaded_plugins = kong.configuration.loaded_plugins + for name, v in pairs(loaded_plugins) do + local res, err = is_plugin_referencing_ca_certificates(name) + if err then + return nil, err + end + + if res then + CA_CERT_REFERENCE_PLUGINS[name] = true + end + end + end + + return CA_CERT_REFERENCE_PLUGINS + end +end + + return { init = init, find_certificate = find_certificate, @@ -378,4 +473,8 @@ return { execute = execute, get_certificate = get_certificate, get_ca_certificate_store = get_ca_certificate_store, + get_ca_certificate_store_for_plugin = get_ca_certificate_store_for_plugin, + ca_ids_cache_key = ca_ids_cache_key, + get_ca_certificate_reference_entities = get_ca_certificate_reference_entities, + get_ca_certificate_reference_plugins = get_ca_certificate_reference_plugins, } diff --git a/kong/runloop/events.lua b/kong/runloop/events.lua index 6e6b42c0db3..1b0d177c0bc 100644 --- a/kong/runloop/events.lua +++ b/kong/runloop/events.lua @@ -319,6 +319,56 @@ local function crud_wasm_handler(data, schema_name) end +local function crud_ca_certificates_handler(data) + if data.operation ~= "update" then + return + end + + log(DEBUG, "[events] CA certificate updated, invalidating ca certificate store caches") + + local ca_id = data.entity.id + + local done_keys = {} + for _, entity in ipairs(certificate.get_ca_certificate_reference_entities()) do + local elements, err = kong.db[entity]:select_by_ca_certificate(ca_id) + if err then + log(ERR, "[events] failed to select ", entity, " by ca certificate ", ca_id, ": ", err) + return + end + + if elements then + for _, e in ipairs(elements) do + local key = certificate.ca_ids_cache_key(e.ca_certificates) + + if not done_keys[key] then + done_keys[key] = true + kong.core_cache:invalidate(key) + end + end + end + end + + local plugin_done_keys = {} + local plugins, err = kong.db.plugins:select_by_ca_certificate(ca_id, nil, + certificate.get_ca_certificate_reference_plugins()) + if err then + log(ERR, "[events] failed to select plugins by ca certificate ", ca_id, ": ", err) + return + end + + if plugins then + for _, e in ipairs(plugins) do + local key = certificate.ca_ids_cache_key(e.config.ca_certificates) + + if not plugin_done_keys[key] then + plugin_done_keys[key] = true + kong.cache:invalidate(key) + end + end + end +end + + local LOCAL_HANDLERS = { { "dao:crud", nil , dao_crud_handler }, @@ -338,6 +388,9 @@ local LOCAL_HANDLERS = { { "crud" , "filter_chains" , crud_wasm_handler }, { "crud" , "services" , crud_wasm_handler }, { "crud" , "routes" , crud_wasm_handler }, + + -- ca certificate store caches invalidations + { "crud" , "ca_certificates" , crud_ca_certificates_handler }, } diff --git a/spec/02-integration/03-db/03-plugins_spec.lua b/spec/02-integration/03-db/03-plugins_spec.lua index b844835cac2..febe2e8519d 100644 --- a/spec/02-integration/03-db/03-plugins_spec.lua +++ b/spec/02-integration/03-db/03-plugins_spec.lua @@ -1,5 +1,72 @@ local helpers = require "spec.helpers" - +local ssl_fixtures = require "spec.fixtures.ssl" + +local ca_cert2 = [[ +-----BEGIN CERTIFICATE----- +MIIEvjCCAqagAwIBAgIJALabx/Nup200MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV +BAMMCFlvbG80Mi4xMCAXDTE5MDkxNTE2Mjc1M1oYDzIxMTkwODIyMTYyNzUzWjAT +MREwDwYDVQQDDAhZb2xvNDIuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANIW67Ay0AtTeBY2mORaGet/VPL5jnBRz0zkZ4Jt7fEq3lbxYaJBnFI8wtz3 +bHLtLsxkvOFujEMY7HVd+iTqbJ7hLBtK0AdgXDjf+HMmoWM7x0PkZO+3XSqyRBbI +YNoEaQvYBNIXrKKJbXIU6higQaXYszeN8r3+RIbcTIlZxy28msivEGfGTrNujQFc +r/eyf+TLHbRqh0yg4Dy/U/T6fqamGhFrjupRmOMugwF/BHMH2JHhBYkkzuZLgV2u +7Yh1S5FRlh11am5vWuRSbarnx72hkJ99rUb6szOWnJKKew8RSn3CyhXbS5cb0QRc +ugRc33p/fMucJ4mtCJ2Om1QQe83G1iV2IBn6XJuCvYlyWH8XU0gkRxWD7ZQsl0bB +8AFTkVsdzb94OM8Y6tWI5ybS8rwl8b3r3fjyToIWrwK4WDJQuIUx4nUHObDyw+KK ++MmqwpAXQWbNeuAc27FjuJm90yr/163aGuInNY5Wiz6CM8WhFNAi/nkEY2vcxKKx +irSdSTkbnrmLFAYrThaq0BWTbW2mwkOatzv4R2kZzBUOiSjRLPnbyiPhI8dHLeGs +wMxiTXwyPi8iQvaIGyN4DPaSEiZ1GbexyYFdP7sJJD8tG8iccbtJYquq3cDaPTf+ +qv5M6R/JuMqtUDheLSpBNK+8vIe5e3MtGFyrKqFXdynJtfHVAgMBAAGjEzARMA8G +A1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggIBAK0BmL5B1fPSMbFy8Hbc +/ESEunt4HGaRWmZZSa/aOtTjhKyDXLLJZz3C4McugfOf9BvvmAOZU4uYjfHTnNH2 +Z3neBkdTpQuJDvrBPNoCtJns01X/nuqFaTK/Tt9ZjAcVeQmp51RwhyiD7nqOJ/7E +Hp2rC6gH2ABXeexws4BDoZPoJktS8fzGWdFBCHzf4mCJcb4XkI+7GTYpglR818L3 +dMNJwXeuUsmxxKScBVH6rgbgcEC/6YwepLMTHB9VcH3X5VCfkDIyPYLWmvE0gKV7 +6OU91E2Rs8PzbJ3EuyQpJLxFUQp8ohv5zaNBlnMb76UJOPR6hXfst5V+e7l5Dgwv +Dh4CeO46exmkEsB+6R3pQR8uOFtubH2snA0S3JA1ji6baP5Y9Wh9bJ5McQUgbAPE +sCRBFoDLXOj3EgzibohC5WrxN3KIMxlQnxPl3VdQvp4gF899mn0Z9V5dAsGPbxRd +quE+DwfXkm0Sa6Ylwqrzu2OvSVgbMliF3UnWbNsDD5KcHGIaFxVC1qkwK4cT3pyS +58i/HAB2+P+O+MltQUDiuw0OSUFDC0IIjkDfxLVffbF+27ef9C5NG81QlwTz7TuN +zeigcsBKooMJTszxCl6dtxSyWTj7hJWXhy9pXsm1C1QulG6uT4RwCa3m0QZoO7G+ +6Wu6lP/kodPuoNubstIuPdi2 +-----END CERTIFICATE----- +]] + +local other_ca_cert = [[ +-----BEGIN CERTIFICATE----- +MIIFrTCCA5WgAwIBAgIUFQe9z25yjw26iWzS+P7+hz1zx6AwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG +A1UECgwES29uZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEDAOBgNVBAMMB3Jvb3Rf +Y2EwHhcNMjEwMzA0MTEyMjM0WhcNNDEwMjI3MTEyMjM0WjBeMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQKDARLb25nMRQwEgYD +VQQLDAtFbmdpbmVlcmluZzEQMA4GA1UEAwwHcm9vdF9jYTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKKjido39I5SEmPhme0Z+hG0buOylXg+jmqHpJ/K +rs+dSq/PsJCjSke81eOP2MFa5duyBxdnXmMJwZYxuQ91bKxdzWVE9ZgCJgNJYsB6 +y5+Fe7ypERwa2ebS/M99FFJ3EzpF017XdsgnSfVh1GEQOZkWQ1+7YrEUEgtwN5lO +MVUmj1EfoL+jQ/zwxwdxpLu3dh3Ica3szmx3YxqIPRnpyoYYqbktjL63gmFCjLeW +zEXdVZyoisdaA4iZ9e/wmuLR2/F4cbZ0SjU7QULZ2Zt/SCrs3CaJ3/ZAa6s84kjg +JBMav+GxbvATSuWQEajiVQrkW9HvXD/NUQBCzzZsOfpzn0044Ls7XvWDCCXs+xtG +Uhd5cJfmlcbHbZ9PU1xTBqdbwiRX+XlmX7CJRcfgnYnU/B3m5IheA1XKYhoXikgv +geRwq5uZ8Z2E/WONmFts46MLSmH43Ft+gIXA1u1g3eDHkU2bx9u592lZoluZtL3m +bmebyk+5bd0GdiHjBGvDSCf/fgaWROgGO9e0PBgdsngHEFmRspipaH39qveM1Cdh +83q4I96BRmjU5tvFXydFCvp8ABpZz9Gj0h8IRP+bK5ukU46YrEIxQxjBee1c1AAb +oatRJSJc2J6zSYXRnQfwf5OkhpmVYc+1TAyqPBfixa2TQ7OOhXxDYsJHAb7WySKP +lfonAgMBAAGjYzBhMB0GA1UdDgQWBBT00Tua7un0KobEs1aXuSZV8x4Q7TAfBgNV +HSMEGDAWgBT00Tua7un0KobEs1aXuSZV8x4Q7TAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAgI8CSmjvzQgmnzcNwqX5 +o+KBWEMHJEqQfowaZE7o6xkvEljb1YHRDE0hlwUtD1vbKUthoHD8Mqim3No5z4J0 +dEE+mXQ3zlJWKl5gqHs9KtcLhk51mf4VJ2TW8Z7AoE2OjWSnycLNdlpqUvxzCQOn +CIhvyDfs4OV1RYywbfiLLmzTCYT7Mt5ye1ZafoRNZ37DCnI/uqoOaMb+a6VaE+0F +ZXlDonXmy54QUmt6foSG/+kYaqdVLribsE6H+GpePmPTKKOvgE1RutR5+nvMJUB3 ++zMQSPVVYLzizwV+Tq9il81qNQB2hZGvM8iSRraBNn8mwpx7M6kcoJ4gvCA3kHCI +rmuuzlhkNcmZYh0uG378CzhdEOV+JMmuCh4xt2SbQIr5Luqm/+Xoq4tDplKoUVkC +DScxPoFNoi9bZYW/ppcaeX5KT3Gt0JBaCfD7d0CtbUp/iPS1HtgXTIL9XiYPipsV +oPLtqvfeORl6aUuqs1xX8HvZrSgcld51+r8X31YIs6feYTFvlbfP0/Jhf2Cs0K/j +jhC0sGVdWO1C0akDlEBfuE5YMrehjYrrOnEavtTi9+H0vNaB+BGAJHIAj+BGj5C7 +0EkbQdEyhB0pliy9qzbPtN5nt+y0I1lgN9VlFMub6r1u5novNzuVm+5ceBrxG+ga +T6nsr9aTE1yghO6GTWEPssw= +-----END CERTIFICATE----- +]] assert:set_parameter("TableFormatLevel", 10) @@ -11,12 +78,18 @@ for _, strategy in helpers.each_strategy() do describe("kong.db [#" .. strategy .. "]", function() local db, bp, service, route local global_plugin + local ca1, ca2, other_ca + local routes = {} + local p1, p2, p3, p4, p5, p6 lazy_setup(function() bp, db = helpers.get_db_utils(strategy, { "routes", "services", "plugins", + "ca_certificates", + }, { + "reference-ca-cert", }) global_plugin = db.plugins:insert({ name = "key-auth", @@ -24,6 +97,71 @@ for _, strategy in helpers.each_strategy() do }) assert.truthy(global_plugin) + ca1 = assert(bp.ca_certificates:insert({ + cert = ssl_fixtures.cert_ca, + })) + + ca2 = assert(bp.ca_certificates:insert({ + cert = ca_cert2, + })) + + other_ca = assert(bp.ca_certificates:insert({ + cert = other_ca_cert, + })) + + for i = 1, 6 do + routes[i] = assert(bp.routes:insert({ + paths = { "/foo" .. i, }, + })) + end + + p1 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[1], + config = { + ca_certificates = { ca1.id }, + } + })) + + p2 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[2], + config = { + ca_certificates = { ca1.id }, + } + })) + + p3 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[3], + config = { + ca_certificates = { ca2.id }, + } + })) + + p4 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[4], + config = { + ca_certificates = { ca2.id }, + } + })) + + p5 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[5], + config = { + ca_certificates = { ca1.id, ca2.id }, + } + })) + + p6 = assert(bp.plugins:insert({ + name = "reference-ca-cert", + route = routes[6], + config = { + ca_certificates = { ca1.id, ca2.id }, + } + })) end) describe("Plugins #plugins", function() @@ -303,6 +441,162 @@ for _, strategy in helpers.each_strategy() do end) + describe(":select_by_ca_certificate()", function() + it("selects the correct plugins", function() + local plugins, err = db.plugins:select_by_ca_certificate(ca1.id, nil, { + ["reference-ca-cert"] = true, + }) + local expected = { + [p1.id] = true, + [p2.id] = true, + [p5.id] = true, + [p6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(plugins) + assert(#plugins == 4) + + for _, p in ipairs(plugins) do + res[p.id] = true + end + assert.are.same(expected, res) + + local plugins, err = db.plugins:select_by_ca_certificate(ca2.id, nil, { + ["reference-ca-cert"] = true, + }) + local expected = { + [p3.id] = true, + [p4.id] = true, + [p5.id] = true, + [p6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(plugins) + assert(#plugins == 4) + + for _, p in ipairs(plugins) do + res[p.id] = true + end + assert.are.same(expected, res) + + -- unreferenced ca certificate + local plugins, err = db.plugins:select_by_ca_certificate(other_ca.id, nil, { + ["reference-ca-cert"] = true, + }) + assert.is_nil(err) + assert(plugins) + assert(#plugins == 0) + end) + + it("plugin_names default to all plugins", function() + local plugins, err = db.plugins:select_by_ca_certificate(ca1.id, nil) + local expected = { + [p1.id] = true, + [p2.id] = true, + [p5.id] = true, + [p6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(plugins) + assert(#plugins == 4) + + for _, p in ipairs(plugins) do + res[p.id] = true + end + assert.are.same(expected, res) + + local plugins, err = db.plugins:select_by_ca_certificate(ca2.id, nil) + local expected = { + [p3.id] = true, + [p4.id] = true, + [p5.id] = true, + [p6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(plugins) + assert(#plugins == 4) + + for _, p in ipairs(plugins) do + res[p.id] = true + end + assert.are.same(expected, res) + + -- unreferenced ca certificate + local plugins, err = db.plugins:select_by_ca_certificate(other_ca.id, nil) + assert.is_nil(err) + assert(plugins) + assert(#plugins == 0) + end) + + it("limits the number of returned plugins", function() + local plugins, err = db.plugins:select_by_ca_certificate(ca1.id, 1, { + ["reference-ca-cert"] = true, + }) + local expected = { + [p1.id] = true, + [p2.id] = true, + [p5.id] = true, + [p6.id] = true, + } + assert.is_nil(err) + assert(plugins) + assert(#plugins == 1) + assert(expected[plugins[1].id]) + + local plugins, err = db.plugins:select_by_ca_certificate(ca2.id, 1, { + ["reference-ca-cert"] = true, + }) + local expected = { + [p3.id] = true, + [p4.id] = true, + [p5.id] = true, + [p6.id] = true, + } + assert.is_nil(err) + assert(plugins) + assert(#plugins == 1) + assert(expected[plugins[1].id]) + + -- unreferenced ca certificate + local plugins, err = db.plugins:select_by_ca_certificate(other_ca.id, 1, { + ["reference-ca-cert"] = true, + }) + assert.is_nil(err) + assert(plugins) + assert(#plugins == 0) + end) + + it("plugin_names supports string type", function() + local plugins, err = db.plugins:select_by_ca_certificate(ca1.id, nil, "reference-ca-cert") + local expected = { + [p1.id] = true, + [p2.id] = true, + [p5.id] = true, + [p6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(plugins) + assert(#plugins == 4) + + for _, p in ipairs(plugins) do + res[p.id] = true + end + assert.are.same(expected, res) + end) + + it("return empty table when plugin doesn't reference ca_certificates", function() + local plugins, err = db.plugins:select_by_ca_certificate(ca1.id, nil, "key-auth") + assert.is_nil(err) + assert(plugins) + assert(#plugins == 0) + end) + + end) end) -- kong.db [strategy] end diff --git a/spec/02-integration/03-db/21-services_spec.lua b/spec/02-integration/03-db/21-services_spec.lua new file mode 100644 index 00000000000..0eede2e3d44 --- /dev/null +++ b/spec/02-integration/03-db/21-services_spec.lua @@ -0,0 +1,215 @@ +local helpers = require "spec.helpers" +local ssl_fixtures = require "spec.fixtures.ssl" + +local ca_cert2 = [[ +-----BEGIN CERTIFICATE----- +MIIEvjCCAqagAwIBAgIJALabx/Nup200MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV +BAMMCFlvbG80Mi4xMCAXDTE5MDkxNTE2Mjc1M1oYDzIxMTkwODIyMTYyNzUzWjAT +MREwDwYDVQQDDAhZb2xvNDIuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANIW67Ay0AtTeBY2mORaGet/VPL5jnBRz0zkZ4Jt7fEq3lbxYaJBnFI8wtz3 +bHLtLsxkvOFujEMY7HVd+iTqbJ7hLBtK0AdgXDjf+HMmoWM7x0PkZO+3XSqyRBbI +YNoEaQvYBNIXrKKJbXIU6higQaXYszeN8r3+RIbcTIlZxy28msivEGfGTrNujQFc +r/eyf+TLHbRqh0yg4Dy/U/T6fqamGhFrjupRmOMugwF/BHMH2JHhBYkkzuZLgV2u +7Yh1S5FRlh11am5vWuRSbarnx72hkJ99rUb6szOWnJKKew8RSn3CyhXbS5cb0QRc +ugRc33p/fMucJ4mtCJ2Om1QQe83G1iV2IBn6XJuCvYlyWH8XU0gkRxWD7ZQsl0bB +8AFTkVsdzb94OM8Y6tWI5ybS8rwl8b3r3fjyToIWrwK4WDJQuIUx4nUHObDyw+KK ++MmqwpAXQWbNeuAc27FjuJm90yr/163aGuInNY5Wiz6CM8WhFNAi/nkEY2vcxKKx +irSdSTkbnrmLFAYrThaq0BWTbW2mwkOatzv4R2kZzBUOiSjRLPnbyiPhI8dHLeGs +wMxiTXwyPi8iQvaIGyN4DPaSEiZ1GbexyYFdP7sJJD8tG8iccbtJYquq3cDaPTf+ +qv5M6R/JuMqtUDheLSpBNK+8vIe5e3MtGFyrKqFXdynJtfHVAgMBAAGjEzARMA8G +A1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggIBAK0BmL5B1fPSMbFy8Hbc +/ESEunt4HGaRWmZZSa/aOtTjhKyDXLLJZz3C4McugfOf9BvvmAOZU4uYjfHTnNH2 +Z3neBkdTpQuJDvrBPNoCtJns01X/nuqFaTK/Tt9ZjAcVeQmp51RwhyiD7nqOJ/7E +Hp2rC6gH2ABXeexws4BDoZPoJktS8fzGWdFBCHzf4mCJcb4XkI+7GTYpglR818L3 +dMNJwXeuUsmxxKScBVH6rgbgcEC/6YwepLMTHB9VcH3X5VCfkDIyPYLWmvE0gKV7 +6OU91E2Rs8PzbJ3EuyQpJLxFUQp8ohv5zaNBlnMb76UJOPR6hXfst5V+e7l5Dgwv +Dh4CeO46exmkEsB+6R3pQR8uOFtubH2snA0S3JA1ji6baP5Y9Wh9bJ5McQUgbAPE +sCRBFoDLXOj3EgzibohC5WrxN3KIMxlQnxPl3VdQvp4gF899mn0Z9V5dAsGPbxRd +quE+DwfXkm0Sa6Ylwqrzu2OvSVgbMliF3UnWbNsDD5KcHGIaFxVC1qkwK4cT3pyS +58i/HAB2+P+O+MltQUDiuw0OSUFDC0IIjkDfxLVffbF+27ef9C5NG81QlwTz7TuN +zeigcsBKooMJTszxCl6dtxSyWTj7hJWXhy9pXsm1C1QulG6uT4RwCa3m0QZoO7G+ +6Wu6lP/kodPuoNubstIuPdi2 +-----END CERTIFICATE----- +]] + +local other_ca_cert = [[ +-----BEGIN CERTIFICATE----- +MIIFrTCCA5WgAwIBAgIUFQe9z25yjw26iWzS+P7+hz1zx6AwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG +A1UECgwES29uZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEDAOBgNVBAMMB3Jvb3Rf +Y2EwHhcNMjEwMzA0MTEyMjM0WhcNNDEwMjI3MTEyMjM0WjBeMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQKDARLb25nMRQwEgYD +VQQLDAtFbmdpbmVlcmluZzEQMA4GA1UEAwwHcm9vdF9jYTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKKjido39I5SEmPhme0Z+hG0buOylXg+jmqHpJ/K +rs+dSq/PsJCjSke81eOP2MFa5duyBxdnXmMJwZYxuQ91bKxdzWVE9ZgCJgNJYsB6 +y5+Fe7ypERwa2ebS/M99FFJ3EzpF017XdsgnSfVh1GEQOZkWQ1+7YrEUEgtwN5lO +MVUmj1EfoL+jQ/zwxwdxpLu3dh3Ica3szmx3YxqIPRnpyoYYqbktjL63gmFCjLeW +zEXdVZyoisdaA4iZ9e/wmuLR2/F4cbZ0SjU7QULZ2Zt/SCrs3CaJ3/ZAa6s84kjg +JBMav+GxbvATSuWQEajiVQrkW9HvXD/NUQBCzzZsOfpzn0044Ls7XvWDCCXs+xtG +Uhd5cJfmlcbHbZ9PU1xTBqdbwiRX+XlmX7CJRcfgnYnU/B3m5IheA1XKYhoXikgv +geRwq5uZ8Z2E/WONmFts46MLSmH43Ft+gIXA1u1g3eDHkU2bx9u592lZoluZtL3m +bmebyk+5bd0GdiHjBGvDSCf/fgaWROgGO9e0PBgdsngHEFmRspipaH39qveM1Cdh +83q4I96BRmjU5tvFXydFCvp8ABpZz9Gj0h8IRP+bK5ukU46YrEIxQxjBee1c1AAb +oatRJSJc2J6zSYXRnQfwf5OkhpmVYc+1TAyqPBfixa2TQ7OOhXxDYsJHAb7WySKP +lfonAgMBAAGjYzBhMB0GA1UdDgQWBBT00Tua7un0KobEs1aXuSZV8x4Q7TAfBgNV +HSMEGDAWgBT00Tua7un0KobEs1aXuSZV8x4Q7TAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAgI8CSmjvzQgmnzcNwqX5 +o+KBWEMHJEqQfowaZE7o6xkvEljb1YHRDE0hlwUtD1vbKUthoHD8Mqim3No5z4J0 +dEE+mXQ3zlJWKl5gqHs9KtcLhk51mf4VJ2TW8Z7AoE2OjWSnycLNdlpqUvxzCQOn +CIhvyDfs4OV1RYywbfiLLmzTCYT7Mt5ye1ZafoRNZ37DCnI/uqoOaMb+a6VaE+0F +ZXlDonXmy54QUmt6foSG/+kYaqdVLribsE6H+GpePmPTKKOvgE1RutR5+nvMJUB3 ++zMQSPVVYLzizwV+Tq9il81qNQB2hZGvM8iSRraBNn8mwpx7M6kcoJ4gvCA3kHCI +rmuuzlhkNcmZYh0uG378CzhdEOV+JMmuCh4xt2SbQIr5Luqm/+Xoq4tDplKoUVkC +DScxPoFNoi9bZYW/ppcaeX5KT3Gt0JBaCfD7d0CtbUp/iPS1HtgXTIL9XiYPipsV +oPLtqvfeORl6aUuqs1xX8HvZrSgcld51+r8X31YIs6feYTFvlbfP0/Jhf2Cs0K/j +jhC0sGVdWO1C0akDlEBfuE5YMrehjYrrOnEavtTi9+H0vNaB+BGAJHIAj+BGj5C7 +0EkbQdEyhB0pliy9qzbPtN5nt+y0I1lgN9VlFMub6r1u5novNzuVm+5ceBrxG+ga +T6nsr9aTE1yghO6GTWEPssw= +-----END CERTIFICATE----- +]] + +for _, strategy in helpers.each_strategy() do + describe("db.services #" .. strategy, function() + local bp, db + local ca1, ca2, other_ca + local srv1, srv2, srv3, srv4, srv5, srv6 + + lazy_setup(function() + bp, db = helpers.get_db_utils(strategy, { + "services", + "ca_certificates", + }) + + ca1 = assert(bp.ca_certificates:insert({ + cert = ssl_fixtures.cert_ca, + })) + + ca2 = assert(bp.ca_certificates:insert({ + cert = ca_cert2, + })) + + other_ca = assert(bp.ca_certificates:insert({ + cert = other_ca_cert, + })) + + local url = "https://" .. helpers.mock_upstream_host .. ":" .. helpers.mock_upstream_port + + srv1 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca1.id }, + }) + + srv2 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca1.id }, + }) + + srv3 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca2.id }, + }) + + srv4 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca2.id }, + }) + + srv5 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca1.id, ca2.id }, + }) + + srv6 = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca1.id, ca2.id }, + }) + end) + + lazy_teardown(function() + db.services:truncate() + db.ca_certificates:truncate() + end) + + describe("services:select_by_ca_certificate()", function() + it("selects the correct services", function() + local services, err = db.services:select_by_ca_certificate(ca1.id) + local expected = { + [srv1.id] = true, + [srv2.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 4) + + for _, s in ipairs(services) do + res[s.id] = true + end + assert.are.same(expected, res) + + local services, err = db.services:select_by_ca_certificate(ca2.id) + local expected = { + [srv3.id] = true, + [srv4.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 4) + + for _, s in ipairs(services) do + res[s.id] = true + end + assert.are.same(expected, res) + + -- unreferenced ca certificate + local services, err = db.services:select_by_ca_certificate(other_ca.id) + assert.is_nil(err) + assert(services) + assert(#services == 0) + end) + + it("limits the number of returned services", function() + local services, err = db.services:select_by_ca_certificate(ca1.id, 1) + local expected = { + [srv1.id] = true, + [srv2.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + assert.is_nil(err) + assert(services) + assert(#services == 1) + assert(expected[services[1].id]) + + local services, err = db.services:select_by_ca_certificate(ca2.id, 1) + local expected = { + [srv3.id] = true, + [srv4.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + assert.is_nil(err) + assert(services) + assert(#services == 1) + assert(expected[services[1].id]) + + -- unreferenced ca certificate + local services, err = db.services:select_by_ca_certificate(other_ca.id, 1) + assert.is_nil(err) + assert(services) + assert(#services == 0) + end) + end) + end) +end diff --git a/spec/02-integration/03-db/22-ca_certificates_spec.lua b/spec/02-integration/03-db/22-ca_certificates_spec.lua new file mode 100644 index 00000000000..6fd94a4c515 --- /dev/null +++ b/spec/02-integration/03-db/22-ca_certificates_spec.lua @@ -0,0 +1,145 @@ +local helpers = require "spec.helpers" +local ssl_fixtures = require "spec.fixtures.ssl" +local fmt = string.format + +local ca_cert2 = [[ +-----BEGIN CERTIFICATE----- +MIIEvjCCAqagAwIBAgIJALabx/Nup200MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV +BAMMCFlvbG80Mi4xMCAXDTE5MDkxNTE2Mjc1M1oYDzIxMTkwODIyMTYyNzUzWjAT +MREwDwYDVQQDDAhZb2xvNDIuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANIW67Ay0AtTeBY2mORaGet/VPL5jnBRz0zkZ4Jt7fEq3lbxYaJBnFI8wtz3 +bHLtLsxkvOFujEMY7HVd+iTqbJ7hLBtK0AdgXDjf+HMmoWM7x0PkZO+3XSqyRBbI +YNoEaQvYBNIXrKKJbXIU6higQaXYszeN8r3+RIbcTIlZxy28msivEGfGTrNujQFc +r/eyf+TLHbRqh0yg4Dy/U/T6fqamGhFrjupRmOMugwF/BHMH2JHhBYkkzuZLgV2u +7Yh1S5FRlh11am5vWuRSbarnx72hkJ99rUb6szOWnJKKew8RSn3CyhXbS5cb0QRc +ugRc33p/fMucJ4mtCJ2Om1QQe83G1iV2IBn6XJuCvYlyWH8XU0gkRxWD7ZQsl0bB +8AFTkVsdzb94OM8Y6tWI5ybS8rwl8b3r3fjyToIWrwK4WDJQuIUx4nUHObDyw+KK ++MmqwpAXQWbNeuAc27FjuJm90yr/163aGuInNY5Wiz6CM8WhFNAi/nkEY2vcxKKx +irSdSTkbnrmLFAYrThaq0BWTbW2mwkOatzv4R2kZzBUOiSjRLPnbyiPhI8dHLeGs +wMxiTXwyPi8iQvaIGyN4DPaSEiZ1GbexyYFdP7sJJD8tG8iccbtJYquq3cDaPTf+ +qv5M6R/JuMqtUDheLSpBNK+8vIe5e3MtGFyrKqFXdynJtfHVAgMBAAGjEzARMA8G +A1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggIBAK0BmL5B1fPSMbFy8Hbc +/ESEunt4HGaRWmZZSa/aOtTjhKyDXLLJZz3C4McugfOf9BvvmAOZU4uYjfHTnNH2 +Z3neBkdTpQuJDvrBPNoCtJns01X/nuqFaTK/Tt9ZjAcVeQmp51RwhyiD7nqOJ/7E +Hp2rC6gH2ABXeexws4BDoZPoJktS8fzGWdFBCHzf4mCJcb4XkI+7GTYpglR818L3 +dMNJwXeuUsmxxKScBVH6rgbgcEC/6YwepLMTHB9VcH3X5VCfkDIyPYLWmvE0gKV7 +6OU91E2Rs8PzbJ3EuyQpJLxFUQp8ohv5zaNBlnMb76UJOPR6hXfst5V+e7l5Dgwv +Dh4CeO46exmkEsB+6R3pQR8uOFtubH2snA0S3JA1ji6baP5Y9Wh9bJ5McQUgbAPE +sCRBFoDLXOj3EgzibohC5WrxN3KIMxlQnxPl3VdQvp4gF899mn0Z9V5dAsGPbxRd +quE+DwfXkm0Sa6Ylwqrzu2OvSVgbMliF3UnWbNsDD5KcHGIaFxVC1qkwK4cT3pyS +58i/HAB2+P+O+MltQUDiuw0OSUFDC0IIjkDfxLVffbF+27ef9C5NG81QlwTz7TuN +zeigcsBKooMJTszxCl6dtxSyWTj7hJWXhy9pXsm1C1QulG6uT4RwCa3m0QZoO7G+ +6Wu6lP/kodPuoNubstIuPdi2 +-----END CERTIFICATE----- +]] + +local other_ca_cert = [[ +-----BEGIN CERTIFICATE----- +MIIFrTCCA5WgAwIBAgIUFQe9z25yjw26iWzS+P7+hz1zx6AwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG +A1UECgwES29uZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxEDAOBgNVBAMMB3Jvb3Rf +Y2EwHhcNMjEwMzA0MTEyMjM0WhcNNDEwMjI3MTEyMjM0WjBeMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ0wCwYDVQQKDARLb25nMRQwEgYD +VQQLDAtFbmdpbmVlcmluZzEQMA4GA1UEAwwHcm9vdF9jYTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKKjido39I5SEmPhme0Z+hG0buOylXg+jmqHpJ/K +rs+dSq/PsJCjSke81eOP2MFa5duyBxdnXmMJwZYxuQ91bKxdzWVE9ZgCJgNJYsB6 +y5+Fe7ypERwa2ebS/M99FFJ3EzpF017XdsgnSfVh1GEQOZkWQ1+7YrEUEgtwN5lO +MVUmj1EfoL+jQ/zwxwdxpLu3dh3Ica3szmx3YxqIPRnpyoYYqbktjL63gmFCjLeW +zEXdVZyoisdaA4iZ9e/wmuLR2/F4cbZ0SjU7QULZ2Zt/SCrs3CaJ3/ZAa6s84kjg +JBMav+GxbvATSuWQEajiVQrkW9HvXD/NUQBCzzZsOfpzn0044Ls7XvWDCCXs+xtG +Uhd5cJfmlcbHbZ9PU1xTBqdbwiRX+XlmX7CJRcfgnYnU/B3m5IheA1XKYhoXikgv +geRwq5uZ8Z2E/WONmFts46MLSmH43Ft+gIXA1u1g3eDHkU2bx9u592lZoluZtL3m +bmebyk+5bd0GdiHjBGvDSCf/fgaWROgGO9e0PBgdsngHEFmRspipaH39qveM1Cdh +83q4I96BRmjU5tvFXydFCvp8ABpZz9Gj0h8IRP+bK5ukU46YrEIxQxjBee1c1AAb +oatRJSJc2J6zSYXRnQfwf5OkhpmVYc+1TAyqPBfixa2TQ7OOhXxDYsJHAb7WySKP +lfonAgMBAAGjYzBhMB0GA1UdDgQWBBT00Tua7un0KobEs1aXuSZV8x4Q7TAfBgNV +HSMEGDAWgBT00Tua7un0KobEs1aXuSZV8x4Q7TAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAgI8CSmjvzQgmnzcNwqX5 +o+KBWEMHJEqQfowaZE7o6xkvEljb1YHRDE0hlwUtD1vbKUthoHD8Mqim3No5z4J0 +dEE+mXQ3zlJWKl5gqHs9KtcLhk51mf4VJ2TW8Z7AoE2OjWSnycLNdlpqUvxzCQOn +CIhvyDfs4OV1RYywbfiLLmzTCYT7Mt5ye1ZafoRNZ37DCnI/uqoOaMb+a6VaE+0F +ZXlDonXmy54QUmt6foSG/+kYaqdVLribsE6H+GpePmPTKKOvgE1RutR5+nvMJUB3 ++zMQSPVVYLzizwV+Tq9il81qNQB2hZGvM8iSRraBNn8mwpx7M6kcoJ4gvCA3kHCI +rmuuzlhkNcmZYh0uG378CzhdEOV+JMmuCh4xt2SbQIr5Luqm/+Xoq4tDplKoUVkC +DScxPoFNoi9bZYW/ppcaeX5KT3Gt0JBaCfD7d0CtbUp/iPS1HtgXTIL9XiYPipsV +oPLtqvfeORl6aUuqs1xX8HvZrSgcld51+r8X31YIs6feYTFvlbfP0/Jhf2Cs0K/j +jhC0sGVdWO1C0akDlEBfuE5YMrehjYrrOnEavtTi9+H0vNaB+BGAJHIAj+BGj5C7 +0EkbQdEyhB0pliy9qzbPtN5nt+y0I1lgN9VlFMub6r1u5novNzuVm+5ceBrxG+ga +T6nsr9aTE1yghO6GTWEPssw= +-----END CERTIFICATE----- +]] + +for _, strategy in helpers.each_strategy() do + describe("db.services #" .. strategy, function() + local bp, db + local ca1, ca2, other_ca + local service, plugin + + lazy_setup(function() + bp, db = helpers.get_db_utils(strategy, { + "services", + "plugins", + "ca_certificates", + }, { + "reference-ca-cert", + }) + + ca1 = assert(bp.ca_certificates:insert({ + cert = ssl_fixtures.cert_ca, + })) + + ca2 = assert(bp.ca_certificates:insert({ + cert = ca_cert2, + })) + + other_ca = assert(bp.ca_certificates:insert({ + cert = other_ca_cert, + })) + + local url = "https://" .. helpers.mock_upstream_host .. ":" .. helpers.mock_upstream_port + + service = assert(bp.services:insert { + url = url, + protocol = "https", + ca_certificates = { ca1.id }, + }) + + plugin = assert(bp.plugins:insert({ + name = "reference-ca-cert", + service = service, + config = { + ca_certificates = { ca2.id }, + } + })) + end) + + lazy_teardown(function() + db.services:truncate() + db.plugins:truncate() + db.ca_certificates:truncate() + end) + + describe("ca_certificates:delete()", function() + it("can delete ca certificate that is not being referenced", function() + local ok, err, err_t = db.ca_certificates:delete({ id = other_ca.id }) + assert.is_nil(err) + assert.is_nil(err_t) + assert(ok) + end) + + it("can't delete ca certificate that is referenced by services", function() + local ok, err = db.ca_certificates:delete({ id = ca1.id }) + assert.matches(fmt("ca certificate %s is still referenced by services (id = %s)", ca1.id, service.id), + err, nil, true) + assert.is_nil(ok) + end) + + it("can't delete ca certificate that is referenced by plugins", function() + local ok, err = db.ca_certificates:delete({ id = ca2.id }) + assert.matches(fmt("ca certificate %s is still referenced by plugins (id = %s)", ca2.id, plugin.id), + err, nil, true) + assert.is_nil(ok) + end) + end) + end) +end diff --git a/spec/02-integration/04-admin_api/16-ca_certificates_routes_spec.lua b/spec/02-integration/04-admin_api/16-ca_certificates_routes_spec.lua index 10d81b88a3b..fc837000895 100644 --- a/spec/02-integration/04-admin_api/16-ca_certificates_routes_spec.lua +++ b/spec/02-integration/04-admin_api/16-ca_certificates_routes_spec.lua @@ -42,6 +42,7 @@ for _, strategy in helpers.each_strategy() do lazy_setup(function() bp, db = helpers.get_db_utils(strategy, { "ca_certificates", + "services", }) assert(helpers.start_kong { @@ -148,6 +149,32 @@ for _, strategy in helpers.each_strategy() do ca = assert(bp.ca_certificates:insert()) end) + it("not allowed to delete if it is referenced by other entities", function() + -- add a service that references the ca + local res = client:post("/services/", { + body = { + url = "https://" .. helpers.mock_upstream_host .. ":" .. helpers.mock_upstream_port, + protocol = "https", + ca_certificates = { ca.id }, + }, + headers = { ["Content-Type"] = "application/json" }, + }) + local body = assert.res_status(201, res) + local service = cjson.decode(body) + + helpers.wait_for_all_config_update() + + local res = client:delete("/ca_certificates/" .. ca.id) + + local body = assert.res_status(400, res) + local json = cjson.decode(body) + + assert.equal("ca certificate " .. ca.id .. " is still referenced by services (id = " .. service.id .. ")", json.message) + + local res = client:delete("/services/" .. service.id) + assert.res_status(204, res) + end) + it("works", function() local res = client:delete("/ca_certificates/" .. ca.id) assert.res_status(204, res) diff --git a/spec/02-integration/05-proxy/18-upstream_tls_spec.lua b/spec/02-integration/05-proxy/18-upstream_tls_spec.lua index ec1723d9a71..df51053ffb0 100644 --- a/spec/02-integration/05-proxy/18-upstream_tls_spec.lua +++ b/spec/02-integration/05-proxy/18-upstream_tls_spec.lua @@ -3,6 +3,37 @@ local ssl_fixtures = require "spec.fixtures.ssl" local atc_compat = require "kong.router.compat" +local other_ca_cert = [[ +-----BEGIN CERTIFICATE----- +MIIEvjCCAqagAwIBAgIJALabx/Nup200MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV +BAMMCFlvbG80Mi4xMCAXDTE5MDkxNTE2Mjc1M1oYDzIxMTkwODIyMTYyNzUzWjAT +MREwDwYDVQQDDAhZb2xvNDIuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANIW67Ay0AtTeBY2mORaGet/VPL5jnBRz0zkZ4Jt7fEq3lbxYaJBnFI8wtz3 +bHLtLsxkvOFujEMY7HVd+iTqbJ7hLBtK0AdgXDjf+HMmoWM7x0PkZO+3XSqyRBbI +YNoEaQvYBNIXrKKJbXIU6higQaXYszeN8r3+RIbcTIlZxy28msivEGfGTrNujQFc +r/eyf+TLHbRqh0yg4Dy/U/T6fqamGhFrjupRmOMugwF/BHMH2JHhBYkkzuZLgV2u +7Yh1S5FRlh11am5vWuRSbarnx72hkJ99rUb6szOWnJKKew8RSn3CyhXbS5cb0QRc +ugRc33p/fMucJ4mtCJ2Om1QQe83G1iV2IBn6XJuCvYlyWH8XU0gkRxWD7ZQsl0bB +8AFTkVsdzb94OM8Y6tWI5ybS8rwl8b3r3fjyToIWrwK4WDJQuIUx4nUHObDyw+KK ++MmqwpAXQWbNeuAc27FjuJm90yr/163aGuInNY5Wiz6CM8WhFNAi/nkEY2vcxKKx +irSdSTkbnrmLFAYrThaq0BWTbW2mwkOatzv4R2kZzBUOiSjRLPnbyiPhI8dHLeGs +wMxiTXwyPi8iQvaIGyN4DPaSEiZ1GbexyYFdP7sJJD8tG8iccbtJYquq3cDaPTf+ +qv5M6R/JuMqtUDheLSpBNK+8vIe5e3MtGFyrKqFXdynJtfHVAgMBAAGjEzARMA8G +A1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggIBAK0BmL5B1fPSMbFy8Hbc +/ESEunt4HGaRWmZZSa/aOtTjhKyDXLLJZz3C4McugfOf9BvvmAOZU4uYjfHTnNH2 +Z3neBkdTpQuJDvrBPNoCtJns01X/nuqFaTK/Tt9ZjAcVeQmp51RwhyiD7nqOJ/7E +Hp2rC6gH2ABXeexws4BDoZPoJktS8fzGWdFBCHzf4mCJcb4XkI+7GTYpglR818L3 +dMNJwXeuUsmxxKScBVH6rgbgcEC/6YwepLMTHB9VcH3X5VCfkDIyPYLWmvE0gKV7 +6OU91E2Rs8PzbJ3EuyQpJLxFUQp8ohv5zaNBlnMb76UJOPR6hXfst5V+e7l5Dgwv +Dh4CeO46exmkEsB+6R3pQR8uOFtubH2snA0S3JA1ji6baP5Y9Wh9bJ5McQUgbAPE +sCRBFoDLXOj3EgzibohC5WrxN3KIMxlQnxPl3VdQvp4gF899mn0Z9V5dAsGPbxRd +quE+DwfXkm0Sa6Ylwqrzu2OvSVgbMliF3UnWbNsDD5KcHGIaFxVC1qkwK4cT3pyS +58i/HAB2+P+O+MltQUDiuw0OSUFDC0IIjkDfxLVffbF+27ef9C5NG81QlwTz7TuN +zeigcsBKooMJTszxCl6dtxSyWTj7hJWXhy9pXsm1C1QulG6uT4RwCa3m0QZoO7G+ +6Wu6lP/kodPuoNubstIuPdi2 +-----END CERTIFICATE----- +]] + local fixtures = { http_mock = { upstream_mtls = [[ @@ -952,6 +983,129 @@ for _, strategy in helpers.each_strategy() do assert.equals("it works", body) end end) + + it("#db request is not allowed through once the CA certificate is updated to other ca", function() + local res = assert(admin_client:patch("/ca_certificates/" .. ca_certificate.id, { + body = { + cert = other_ca_cert, + }, + headers = { ["Content-Type"] = "application/json" }, + })) + + assert.res_status(200, res) + + wait_for_all_config_update(subsystems) + + local body + helpers.wait_until(function() + local proxy_client = get_proxy_client(subsystems, 19001) + local path + if subsystems == "http" then + path = "/tls" + else + path = "/" + end + local res, err = proxy_client:send { + path = path, + headers = { + ["Host"] = "example.com", + } + } + + if subsystems == "http" then + return pcall(function() + body = assert.res_status(502, res) + assert(proxy_client:close()) + end) + else + return pcall(function() + assert.equals("connection reset by peer", err) + assert(proxy_client:close()) + end) + end + end, 10) + + if subsystems == "http" then + assert.matches("An invalid response was received from the upstream server", body) + end + + -- buffered_proxying + if subsystems == "http" then + helpers.wait_until(function() + local proxy_client = get_proxy_client(subsystems, 19001) + local path = "/tls-buffered-proxying" + local res = proxy_client:send { + path = path, + headers = { + ["Host"] = "example.com", + } + } + + return pcall(function() + body = assert.res_status(502, res) + assert(proxy_client:close()) + end) + end, 10) + assert.matches("An invalid response was received from the upstream server", body) + end + end) + + it("#db request is allowed through once the CA certificate is updated back to the correct ca", function() + local res = assert(admin_client:patch("/ca_certificates/" .. ca_certificate.id, { + body = { + cert = ssl_fixtures.cert_ca, + }, + headers = { ["Content-Type"] = "application/json" }, + })) + + assert.res_status(200, res) + + wait_for_all_config_update(subsystems) + + local body + helpers.wait_until(function() + local proxy_client = get_proxy_client(subsystems, 19001) + local path + if subsystems == "http" then + path = "/tls" + else + path = "/" + end + local res = proxy_client:send { + path = path, + headers = { + ["Host"] = "example.com", + } + } + + return pcall(function() + body = assert.res_status(200, res) + assert(proxy_client:close()) + end) + end, 10) + + assert.equals("it works", body) + + -- buffered_proxying + if subsystems == "http" then + helpers.wait_until(function() + local proxy_client = get_proxy_client(subsystems, 19001) + local path = "/tls-buffered-proxying" + local res = proxy_client:send { + path = path, + headers = { + ["Host"] = "example.com", + } + } + + return pcall(function() + body = assert.res_status(200, res) + assert(proxy_client:close()) + end) + end, 10) + assert.equals("it works", body) + end + end) end) describe("#db tls_verify_depth", function() @@ -1004,19 +1158,17 @@ for _, strategy in helpers.each_strategy() do } } - return pcall(function() - if subsystems == "http" then - return pcall(function() - body = assert.res_status(502, res) - assert(proxy_client:close()) - end) - else - return pcall(function() - assert.equals("connection reset by peer", err) - assert(proxy_client:close()) - end) - end - end) + if subsystems == "http" then + return pcall(function() + body = assert.res_status(502, res) + assert(proxy_client:close()) + end) + else + return pcall(function() + assert.equals("connection reset by peer", err) + assert(proxy_client:close()) + end) + end end, 10) if subsystems == "http" then assert.matches("An invalid response was received from the upstream server", body) diff --git a/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/handler.lua b/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/handler.lua new file mode 100644 index 00000000000..dfff3ebcbd0 --- /dev/null +++ b/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/handler.lua @@ -0,0 +1,6 @@ +local ReferenceCaCertHandler = { + VERSION = "1.0.0", + PRIORITY = 1, +} + +return ReferenceCaCertHandler diff --git a/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/schema.lua b/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/schema.lua new file mode 100644 index 00000000000..8e388fe650a --- /dev/null +++ b/spec/fixtures/custom_plugins/kong/plugins/reference-ca-cert/schema.lua @@ -0,0 +1,15 @@ +return { + name = "reference-ca-cert", + fields = { + { + config = { + type = "record", + fields = { + { pre_key = { type = "string", }, }, + { ca_certificates = { type = "array", required = true, elements = { type = "string", uuid = true, }, }, }, + { post_key = { type = "string", }, }, + }, + }, + }, + }, +}