From 7b907625775e8315ebbc5510748618d0ea462034 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Thu, 19 Oct 2023 17:54:20 +0800 Subject: [PATCH 01/16] 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) --- .../kong/ca_certificates_reference_check.yml | 3 + kong-3.6.0-0.rockspec | 1 + kong/api/routes/ca_certificates.lua | 24 +++ kong/runloop/certificate.lua | 151 +++++++++++++++ kong/runloop/events.lua | 29 +++ .../16-ca_certificates_routes_spec.lua | 27 +++ .../05-proxy/18-upstream_tls_spec.lua | 178 ++++++++++++++++-- 7 files changed, 400 insertions(+), 13 deletions(-) create mode 100644 changelog/unreleased/kong/ca_certificates_reference_check.yml create mode 100644 kong/api/routes/ca_certificates.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 000000000000..3ac9d8a3aab5 --- /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 fd8356805d4b..3222afff5927 100644 --- a/kong-3.6.0-0.rockspec +++ b/kong-3.6.0-0.rockspec @@ -144,6 +144,7 @@ build = { ["kong.api.routes.tags"] = "kong/api/routes/tags.lua", ["kong.api.routes.targets"] = "kong/api/routes/targets.lua", ["kong.api.routes.upstreams"] = "kong/api/routes/upstreams.lua", + ["kong.api.routes.ca_certificates"] = "kong/api/routes/ca_certificates.lua", ["kong.admin_gui"] = "kong/admin_gui/init.lua", ["kong.admin_gui.utils"] = "kong/admin_gui/utils.lua", diff --git a/kong/api/routes/ca_certificates.lua b/kong/api/routes/ca_certificates.lua new file mode 100644 index 000000000000..1b73db3e0660 --- /dev/null +++ b/kong/api/routes/ca_certificates.lua @@ -0,0 +1,24 @@ +local certificates = require "kong.runloop.certificate" +local fmt = string.format +local kong = kong + +return { + ["/ca_certificates/:ca_certificates"] = { + DELETE = function(self, db, helpers, parent) + local ca_id = self.params.ca_certificates + local entity, element_or_err = certificates.check_ca_references(ca_id) + + if entity then + local msg = fmt("ca_certificate %s is still referenced by %s (id = %s)", ca_id, entity, element_or_err.id) + kong.log.notice(msg) + return kong.response.exit(400, { message = msg }) + elseif element_or_err then + local msg = "failed to check_ca_references, " .. element_or_err + kong.log.err(msg) + return kong.response.exit(500, { message = msg }) + end + + return parent() + end, + }, +} diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 53da6b3d8d35..43a9d6d0d44a 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -19,6 +19,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 @@ -28,6 +29,8 @@ local ipairs = ipairs local ngx_md5 = ngx.md5 local ngx_exit = ngx.exit local ngx_ERROR = ngx.ERROR +local null = ngx.null +local fmt = string.format local default_cert_and_key @@ -371,6 +374,151 @@ local function get_ca_certificate_store(ca_ids) end +-- ordinary entities that reference ca certificates +-- the value denotes which cache (kong.cache or kong.core_cache) is used +local CA_CERT_REFERENCE_ENTITIES = { + "services", +} + +-- plugins that reference ca certificates +-- Format: +-- mtls-auth = true +local CA_CERT_REFERENCE_PLUGINS = { +} + +local loaded_plugins +local reference_plugins + +-- Examples: +-- gen_iterator("services") +-- gen_iterator("plugins", { mtls-auth = true }) +-- gen_iterator("plugins", "mtls-auth") +-- We assume the field name is always `ca_certificates` +local function gen_iterator(entity, plugins) + local options = { + workspace = null, + } + + local iter = kong.db[entity]:each(1000, options) + + local function iterator() + local element, err = iter() + if err then + return nil, err + + elseif element == nil then + return nil + + else + if plugins then + if type(plugins) ~= "table" then + plugins = { [plugins] = true } + end + + if plugins[element.name] and element.config.ca_certificates and + next(element.config.ca_certificates) then + return element + else + return iterator() + end + else + if element.ca_certificates and next(element.ca_certificates) then + return element + else + return iterator() + end + end + end + end + + return iterator +end + + +-- returns the first encountered entity element that is referencing `ca_id` +-- otherwise, returns nil, err +local function check_ca_references(ca_id) + for _, entity in ipairs(CA_CERT_REFERENCE_ENTITIES) do + for element, err in gen_iterator(entity) do + if err then + local msg = fmt("failed to list %s: %s", entity, err) + return nil, msg + end + + for _, id in ipairs(element.ca_certificates) do + if id == ca_id then + return entity, element + end + end + end + end + + if not reference_plugins then + reference_plugins = {} + loaded_plugins = loaded_plugins or kong.configuration.loaded_plugins + + for k, _ in pairs(CA_CERT_REFERENCE_PLUGINS) do + if loaded_plugins[k] then + reference_plugins[k] = true + end + end + end + + if next(reference_plugins) then + local entity = "plugins" + for element, err in gen_iterator(entity, reference_plugins) do + if err then + local msg = fmt("failed to list plugins: %s", err) + return nil, msg + end + + for _, id in ipairs(element.config.ca_certificates) do + if id == ca_id then + return entity, element + end + end + end + end +end + + +-- returns an array of entities that are referencing `ca_id` +-- return nil, err when error +-- Examples: +-- get_ca_certificate_references(ca_id, "services") +-- get_ca_certificate_references(ca_id, "plugins", "mtls-auth") +-- +-- Note we don't invalidate the ca store caches here directly because +-- different entities use different caches (kong.cache or kong.core_cache) +-- and use different functions to calculate the ca store cache key. +-- And it's not a good idea to depend on the plugin implementations in Core. +local function get_ca_certificate_references(ca_id, entity, plugin) + local elements = {} + + for element, err in gen_iterator(entity, plugin) do + if err then + local msg = fmt("failed to list %s: %s", entity, err) + return nil, msg + end + + local ca_certificates + if entity == "plugins" then + ca_certificates = element.config.ca_certificates + else + ca_certificates = element.ca_certificates + end + + for _, id in ipairs(ca_certificates) do + if id == ca_id then + tb_insert(elements, element) + end + end + end + + return elements +end + + return { init = init, find_certificate = find_certificate, @@ -378,4 +526,7 @@ return { execute = execute, get_certificate = get_certificate, get_ca_certificate_store = get_ca_certificate_store, + ca_ids_cache_key = ca_ids_cache_key, + check_ca_references = check_ca_references, + get_ca_certificate_references = get_ca_certificate_references, } diff --git a/kong/runloop/events.lua b/kong/runloop/events.lua index 6e6b42c0db37..6c02cbd110f7 100644 --- a/kong/runloop/events.lua +++ b/kong/runloop/events.lua @@ -319,6 +319,32 @@ 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_certificates updated, invalidating ca certificate store caches for services") + + local elements, err = certificate.get_ca_certificate_references(data.entity.id, "services") + if err then + log(ERR, "[events] failed to get ca certificate references, ", err) + end + + if elements then + local done_keys = {} + 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 LOCAL_HANDLERS = { { "dao:crud", nil , dao_crud_handler }, @@ -338,6 +364,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/04-admin_api/16-ca_certificates_routes_spec.lua b/spec/02-integration/04-admin_api/16-ca_certificates_routes_spec.lua index 10d81b88a3b3..f70db23271eb 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 ec1723d9a71e..df51053ffb0f 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) From 06680d494e5d04e5ae3a48c69a63db049faa2692 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Wed, 25 Oct 2023 15:11:55 +0800 Subject: [PATCH 02/16] apply comments --- kong/runloop/certificate.lua | 29 ++++++++++++++++------------- kong/runloop/events.lua | 3 ++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 43a9d6d0d44a..9500654cf277 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -391,14 +391,19 @@ local reference_plugins -- Examples: -- gen_iterator("services") --- gen_iterator("plugins", { mtls-auth = true }) -- gen_iterator("plugins", "mtls-auth") -- We assume the field name is always `ca_certificates` -local function gen_iterator(entity, plugins) +local function gen_iterator(entity, plugin_name) local options = { workspace = null, } + if plugin_name then + options.search_fields = { + name = plugin_name, + } + end + local iter = kong.db[entity]:each(1000, options) local function iterator() @@ -410,17 +415,15 @@ local function gen_iterator(entity, plugins) return nil else - if plugins then - if type(plugins) ~= "table" then - plugins = { [plugins] = true } - end - - if plugins[element.name] and element.config.ca_certificates and - next(element.config.ca_certificates) then + if entity == "plugins" then + -- double check, in case the filter doesn't take effect + if plugin_name and plugin_name == element.name and + element.config.ca_certificates and next(element.config.ca_certificates) then return element else return iterator() end + else if element.ca_certificates and next(element.ca_certificates) then return element @@ -464,9 +467,9 @@ local function check_ca_references(ca_id) end end - if next(reference_plugins) then + for plugin_name, _ in pairs(reference_plugins) do local entity = "plugins" - for element, err in gen_iterator(entity, reference_plugins) do + for element, err in gen_iterator(entity, plugin_name) do if err then local msg = fmt("failed to list plugins: %s", err) return nil, msg @@ -492,10 +495,10 @@ end -- different entities use different caches (kong.cache or kong.core_cache) -- and use different functions to calculate the ca store cache key. -- And it's not a good idea to depend on the plugin implementations in Core. -local function get_ca_certificate_references(ca_id, entity, plugin) +local function get_ca_certificate_references(ca_id, entity, plugin_name) local elements = {} - for element, err in gen_iterator(entity, plugin) do + for element, err in gen_iterator(entity, plugin_name) do if err then local msg = fmt("failed to list %s: %s", entity, err) return nil, msg diff --git a/kong/runloop/events.lua b/kong/runloop/events.lua index 6c02cbd110f7..a951579a206a 100644 --- a/kong/runloop/events.lua +++ b/kong/runloop/events.lua @@ -324,11 +324,12 @@ local function crud_ca_certificates_handler(data) return end - log(DEBUG, "[events] Ca_certificates updated, invalidating ca certificate store caches for services") + log(DEBUG, "[events] CA certificate updated, invalidating ca certificate store caches for services") local elements, err = certificate.get_ca_certificate_references(data.entity.id, "services") if err then log(ERR, "[events] failed to get ca certificate references, ", err) + return end if elements then From b8ab47b5491140baeeaa1789204f872baef2f438 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Wed, 25 Oct 2023 17:01:55 +0800 Subject: [PATCH 03/16] change plugin tables from maps to arrays --- kong/runloop/certificate.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 9500654cf277..e32604e585f0 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -375,14 +375,13 @@ end -- ordinary entities that reference ca certificates --- the value denotes which cache (kong.cache or kong.core_cache) is used local CA_CERT_REFERENCE_ENTITIES = { "services", } -- plugins that reference ca certificates --- Format: --- mtls-auth = true +-- For Example: +-- mtls-auth local CA_CERT_REFERENCE_PLUGINS = { } @@ -460,14 +459,14 @@ local function check_ca_references(ca_id) reference_plugins = {} loaded_plugins = loaded_plugins or kong.configuration.loaded_plugins - for k, _ in pairs(CA_CERT_REFERENCE_PLUGINS) do - if loaded_plugins[k] then - reference_plugins[k] = true + for _, name in ipairs(CA_CERT_REFERENCE_PLUGINS) do + if loaded_plugins[name] then + tb_insert(reference_plugins, name) end end end - for plugin_name, _ in pairs(reference_plugins) do + for _, plugin_name in ipairs(reference_plugins) do local entity = "plugins" for element, err in gen_iterator(entity, plugin_name) do if err then From 6117dc1d7463b0f7f5f830560368cc610ff1f189 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Wed, 25 Oct 2023 17:20:01 +0800 Subject: [PATCH 04/16] fix plugin_name double check --- kong/runloop/certificate.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index e32604e585f0..25271aeb4c62 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -416,7 +416,7 @@ local function gen_iterator(entity, plugin_name) else if entity == "plugins" then -- double check, in case the filter doesn't take effect - if plugin_name and plugin_name == element.name and + if (not plugin_name or plugin_name == element.name) and element.config.ca_certificates and next(element.config.ca_certificates) then return element else From 22c27b55c71b03ccd03eb549a299c056041e577f Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Wed, 25 Oct 2023 17:55:32 +0800 Subject: [PATCH 05/16] remove `search_fields` for now as it is EE-only --- kong/runloop/certificate.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 25271aeb4c62..bbf20cb5d050 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -397,12 +397,6 @@ local function gen_iterator(entity, plugin_name) workspace = null, } - if plugin_name then - options.search_fields = { - name = plugin_name, - } - end - local iter = kong.db[entity]:each(1000, options) local function iterator() From 887970fc608f0d2b60ea541a74282177e7fcf68d Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Thu, 9 Nov 2023 11:56:48 +0800 Subject: [PATCH 06/16] do the iteration and filtering in dao by adding `select_by_ca_certificate` --- kong-3.6.0-0.rockspec | 5 + kong/db/dao/plugins.lua | 18 ++++ kong/db/dao/services.lua | 16 +++ kong/db/schema/entities/services.lua | 1 + kong/db/strategies/off/plugins.lua | 46 +++++++++ kong/db/strategies/off/services.lua | 42 ++++++++ kong/db/strategies/postgres/plugins.lua | 39 +++++++ kong/db/strategies/postgres/services.lua | 20 ++++ kong/runloop/certificate.lua | 124 ++++------------------- kong/runloop/events.lua | 30 +++--- 10 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 kong/db/dao/services.lua create mode 100644 kong/db/strategies/off/plugins.lua create mode 100644 kong/db/strategies/off/services.lua create mode 100644 kong/db/strategies/postgres/plugins.lua create mode 100644 kong/db/strategies/postgres/services.lua diff --git a/kong-3.6.0-0.rockspec b/kong-3.6.0-0.rockspec index 3222afff5927..d54a083e497a 100644 --- a/kong-3.6.0-0.rockspec +++ b/kong-3.6.0-0.rockspec @@ -202,6 +202,7 @@ 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.declarative"] = "kong/db/declarative/init.lua", ["kong.db.declarative.marshaller"] = "kong/db/declarative/marshaller.lua", ["kong.db.declarative.export"] = "kong/db/declarative/export.lua", @@ -241,9 +242,13 @@ 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", + ["kong.db.strategies.off.services"] = "kong/db/strategies/off/services.lua", + ["kong.db.strategies.off.plugins"] = "kong/db/strategies/off/plugins.lua", ["kong.db.migrations.state"] = "kong/db/migrations/state.lua", ["kong.db.migrations.subsystems"] = "kong/db/migrations/subsystems.lua", diff --git a/kong/db/dao/plugins.lua b/kong/db/dao/plugins.lua index f05c31d677a2..cd7d305ca670 100644 --- a/kong/db/dao/plugins.lua +++ b/kong/db/dao/plugins.lua @@ -355,5 +355,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 000000000000..d79c1618e125 --- /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/schema/entities/services.lua b/kong/db/schema/entities/services.lua index 030eb90c4389..cf2954a36770 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/off/plugins.lua b/kong/db/strategies/off/plugins.lua new file mode 100644 index 000000000000..9c85d75314b7 --- /dev/null +++ b/kong/db/strategies/off/plugins.lua @@ -0,0 +1,46 @@ +local null = ngx.null + +local Plugins = {} + +function Plugins:select_by_ca_certificate(ca_id, limit, plugin_names) + local PAGE_SIZE = 100 + local next_offset = nil + local rows, err + local matches = {} + local count = 0 + local options = { workspace = null } + + if type(plugin_names) == string then + plugin_names = { [plugin_names] = true } + end + + repeat + rows, err, next_offset = self:page(PAGE_SIZE, next_offset, options) + if err then + return nil, err + end + for _, row in ipairs(rows) do + if limit and count >= limit then + return matches, nil + end + + if (not plugin_names or plugin_names[row.name]) and + type(row.config) == 'table' and type(row.config.ca_certificates) == "table" then + for _, id in ipairs(row.config.ca_certificates) do + if id == ca_id then + table.insert(matches, row) + count = count + 1 + goto continue + end + end + end + + ::continue:: + end + + until next_offset == nil + + return matches, nil +end + +return Plugins diff --git a/kong/db/strategies/off/services.lua b/kong/db/strategies/off/services.lua new file mode 100644 index 000000000000..41995496e8ce --- /dev/null +++ b/kong/db/strategies/off/services.lua @@ -0,0 +1,42 @@ +local null = ngx.null +local tb_insert = table.insert + +local Services = {} + +function Services:select_by_ca_certificate(ca_id, limit) + local PAGE_SIZE = 100 + local next_offset = nil + local rows, err + local matches = {} + local count = 0 + local options = { workspace = null } + + repeat + rows, err, next_offset = self:page(PAGE_SIZE, next_offset, options) + if err then + return nil, err + end + for _, row in ipairs(rows) do + if limit and count >= limit then + return matches, nil + end + + if type(row.ca_certificates) == 'table' then + for _, id in ipairs(row.ca_certificates) do + if id == ca_id then + tb_insert(matches, row) + count = count + 1 + goto continue + end + end + end + + ::continue:: + end + + until next_offset == nil + + return matches, nil +end + +return Services diff --git a/kong/db/strategies/postgres/plugins.lua b/kong/db/strategies/postgres/plugins.lua new file mode 100644 index 000000000000..1f168ccc3c7d --- /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, 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 000000000000..02393a4249e9 --- /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 bbf20cb5d050..0750a7e58059 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -19,7 +19,6 @@ 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 @@ -29,7 +28,6 @@ local ipairs = ipairs local ngx_md5 = ngx.md5 local ngx_exit = ngx.exit local ngx_ERROR = ngx.ERROR -local null = ngx.null local fmt = string.format @@ -385,133 +383,45 @@ local CA_CERT_REFERENCE_ENTITIES = { local CA_CERT_REFERENCE_PLUGINS = { } -local loaded_plugins local reference_plugins --- Examples: --- gen_iterator("services") --- gen_iterator("plugins", "mtls-auth") --- We assume the field name is always `ca_certificates` -local function gen_iterator(entity, plugin_name) - local options = { - workspace = null, - } - - local iter = kong.db[entity]:each(1000, options) - - local function iterator() - local element, err = iter() - if err then - return nil, err - - elseif element == nil then - return nil - - else - if entity == "plugins" then - -- double check, in case the filter doesn't take effect - if (not plugin_name or plugin_name == element.name) and - element.config.ca_certificates and next(element.config.ca_certificates) then - return element - else - return iterator() - end - - else - if element.ca_certificates and next(element.ca_certificates) then - return element - else - return iterator() - end - end - end - end - - return iterator -end - - -- returns the first encountered entity element that is referencing `ca_id` -- otherwise, returns nil, err local function check_ca_references(ca_id) for _, entity in ipairs(CA_CERT_REFERENCE_ENTITIES) do - for element, err in gen_iterator(entity) do - if err then - local msg = fmt("failed to list %s: %s", entity, err) - return nil, msg - end + local elements, err = kong.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 - for _, id in ipairs(element.ca_certificates) do - if id == ca_id then - return entity, element - end - end + if type(elements) == "table" and #elements > 0 then + return entity, elements[1] end end if not reference_plugins then reference_plugins = {} - loaded_plugins = loaded_plugins or kong.configuration.loaded_plugins + local loaded_plugins = kong.configuration.loaded_plugins for _, name in ipairs(CA_CERT_REFERENCE_PLUGINS) do if loaded_plugins[name] then - tb_insert(reference_plugins, name) + reference_plugins[name] = true end end end - for _, plugin_name in ipairs(reference_plugins) do - local entity = "plugins" - for element, err in gen_iterator(entity, plugin_name) do - if err then - local msg = fmt("failed to list plugins: %s", err) - return nil, msg - end - - for _, id in ipairs(element.config.ca_certificates) do - if id == ca_id then - return entity, element - end - end - end + local plugins, err = kong.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 -end - - --- returns an array of entities that are referencing `ca_id` --- return nil, err when error --- Examples: --- get_ca_certificate_references(ca_id, "services") --- get_ca_certificate_references(ca_id, "plugins", "mtls-auth") --- --- Note we don't invalidate the ca store caches here directly because --- different entities use different caches (kong.cache or kong.core_cache) --- and use different functions to calculate the ca store cache key. --- And it's not a good idea to depend on the plugin implementations in Core. -local function get_ca_certificate_references(ca_id, entity, plugin_name) - local elements = {} - - for element, err in gen_iterator(entity, plugin_name) do - if err then - local msg = fmt("failed to list %s: %s", entity, err) - return nil, msg - end - - local ca_certificates - if entity == "plugins" then - ca_certificates = element.config.ca_certificates - else - ca_certificates = element.ca_certificates - end - for _, id in ipairs(ca_certificates) do - if id == ca_id then - tb_insert(elements, element) - end - end + if type(plugins) == "table" and #plugins > 0 then + return "plugins", plugins[1] end - return elements + return nil, nil end @@ -524,5 +434,5 @@ return { get_ca_certificate_store = get_ca_certificate_store, ca_ids_cache_key = ca_ids_cache_key, check_ca_references = check_ca_references, - get_ca_certificate_references = get_ca_certificate_references, + CA_CERT_REFERENCE_ENTITIES = CA_CERT_REFERENCE_ENTITIES, } diff --git a/kong/runloop/events.lua b/kong/runloop/events.lua index a951579a206a..5d4ac22a35b7 100644 --- a/kong/runloop/events.lua +++ b/kong/runloop/events.lua @@ -324,22 +324,26 @@ local function crud_ca_certificates_handler(data) return end - log(DEBUG, "[events] CA certificate updated, invalidating ca certificate store caches for services") + log(DEBUG, "[events] CA certificate updated, invalidating ca certificate store caches") - local elements, err = certificate.get_ca_certificate_references(data.entity.id, "services") - if err then - log(ERR, "[events] failed to get ca certificate references, ", err) - return - end + local ca_id = data.entity.id + + local done_keys = {} + for _, entity in ipairs(certificate.CA_CERT_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 - local done_keys = {} - for _, e in ipairs(elements) do - local key = certificate.ca_ids_cache_key(e.ca_certificates) + 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) + if not done_keys[key] then + done_keys[key] = true + kong.core_cache:invalidate(key) + end end end end From 0119086593f094adaa60a9a01cf234434db4571c Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Fri, 10 Nov 2023 15:55:59 +0800 Subject: [PATCH 07/16] 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 --- kong-3.6.0-0.rockspec | 2 +- kong/api/endpoints.lua | 1 + kong/api/routes/ca_certificates.lua | 24 -------- kong/db/dao/ca_certificates.lua | 53 ++++++++++++++++ kong/db/errors.lua | 11 ++++ kong/runloop/certificate.lua | 96 ++++++++++++++++------------- kong/runloop/events.lua | 21 ++++++- 7 files changed, 140 insertions(+), 68 deletions(-) delete mode 100644 kong/api/routes/ca_certificates.lua create mode 100644 kong/db/dao/ca_certificates.lua diff --git a/kong-3.6.0-0.rockspec b/kong-3.6.0-0.rockspec index d54a083e497a..809e11d035ac 100644 --- a/kong-3.6.0-0.rockspec +++ b/kong-3.6.0-0.rockspec @@ -144,7 +144,6 @@ build = { ["kong.api.routes.tags"] = "kong/api/routes/tags.lua", ["kong.api.routes.targets"] = "kong/api/routes/targets.lua", ["kong.api.routes.upstreams"] = "kong/api/routes/upstreams.lua", - ["kong.api.routes.ca_certificates"] = "kong/api/routes/ca_certificates.lua", ["kong.admin_gui"] = "kong/admin_gui/init.lua", ["kong.admin_gui.utils"] = "kong/admin_gui/utils.lua", @@ -203,6 +202,7 @@ build = { ["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", diff --git a/kong/api/endpoints.lua b/kong/api/endpoints.lua index 0ca7dbe8ccc1..ff96fec1688c 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.REFERENCCED_BY_OTHERS] = 400, } local TAGS_AND_REGEX diff --git a/kong/api/routes/ca_certificates.lua b/kong/api/routes/ca_certificates.lua deleted file mode 100644 index 1b73db3e0660..000000000000 --- a/kong/api/routes/ca_certificates.lua +++ /dev/null @@ -1,24 +0,0 @@ -local certificates = require "kong.runloop.certificate" -local fmt = string.format -local kong = kong - -return { - ["/ca_certificates/:ca_certificates"] = { - DELETE = function(self, db, helpers, parent) - local ca_id = self.params.ca_certificates - local entity, element_or_err = certificates.check_ca_references(ca_id) - - if entity then - local msg = fmt("ca_certificate %s is still referenced by %s (id = %s)", ca_id, entity, element_or_err.id) - kong.log.notice(msg) - return kong.response.exit(400, { message = msg }) - elseif element_or_err then - local msg = "failed to check_ca_references, " .. element_or_err - kong.log.err(msg) - return kong.response.exit(500, { message = msg }) - end - - return parent() - end, - }, -} diff --git a/kong/db/dao/ca_certificates.lua b/kong/db/dao/ca_certificates.lua new file mode 100644 index 000000000000..a76d77e66097 --- /dev/null +++ b/kong/db/dao/ca_certificates.lua @@ -0,0 +1,53 @@ +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(cert_pk) + for _, entity in ipairs(certificate.get_ca_certificate_reference_entities()) do + local elements, err = self.db[entity]:select_by_ca_certificate(cert_pk, 1) + if err then + local msg = fmt("failed to select %s by ca certificate %s: %s", entity, cert_pk, err) + return nil, msg + end + + if type(elements) == "table" and #elements > 0 then + return entity, elements[1] + end + end + + local plugins, err = self.db.plugins:select_by_ca_certificate(cert_pk, 1, + certificate.get_ca_certificate_reference_plugins()) + if err then + local msg = fmt("failed to select plugins by ca_certificate %s: %s", cert_pk, err) + return nil, msg + end + + if type(plugins) == "table" and #plugins > 0 then + return "plugins", plugins[1] + 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) + if entity then + local msg = fmt("ca certificate %s is still referenced by %s (id = %s)", + cert_pk, 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/errors.lua b/kong/db/errors.lua index e5c01f3473f5..c66b9fc857cd 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 + REFERENCCED_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.REFERENCCED_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.REFERENCCED_BY_OTHERS, err) +end + + local flatten_errors do local function singular(noun) diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 0750a7e58059..383fe2f58ef2 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 @@ -28,7 +32,6 @@ local ipairs = ipairs local ngx_md5 = ngx.md5 local ngx_exit = ngx.exit local ngx_ERROR = ngx.ERROR -local fmt = string.format local default_cert_and_key @@ -372,56 +375,64 @@ local function get_ca_certificate_store(ca_ids) end --- ordinary entities that reference ca certificates -local CA_CERT_REFERENCE_ENTITIES = { - "services", -} - --- plugins that reference ca certificates --- For Example: --- mtls-auth -local CA_CERT_REFERENCE_PLUGINS = { -} +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 -local reference_plugins --- returns the first encountered entity element that is referencing `ca_id` --- otherwise, returns nil, err -local function check_ca_references(ca_id) - for _, entity in ipairs(CA_CERT_REFERENCE_ENTITIES) do - local elements, err = kong.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 +-- here we assume the field name is always `ca_certificates` +local get_ca_certificate_reference_entities +do + -- 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 entity_schema = require("kong.db.schema.entities." .. entity_name) + if entity_schema.fields.ca_certificates then + tb_insert(CA_CERT_REFERENCE_ENTITIES, entity_name) + end + end end - if type(elements) == "table" and #elements > 0 then - return entity, elements[1] - end + return CA_CERT_REFERENCE_ENTITIES end +end - if not reference_plugins then - reference_plugins = {} - local loaded_plugins = kong.configuration.loaded_plugins - for _, name in ipairs(CA_CERT_REFERENCE_PLUGINS) do - if loaded_plugins[name] then - reference_plugins[name] = true +-- here we assume the field name is always `ca_certificates` +local get_ca_certificate_reference_plugins +do + -- 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, _ in pairs(loaded_plugins) do + 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 + + if schema.fields.config.fields.ca_certificates then + CA_CERT_REFERENCE_PLUGINS[name] = true + end end end - end - local plugins, err = kong.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] + return CA_CERT_REFERENCE_PLUGINS end - - return nil, nil end @@ -432,7 +443,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, - check_ca_references = check_ca_references, - CA_CERT_REFERENCE_ENTITIES = CA_CERT_REFERENCE_ENTITIES, + 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 5d4ac22a35b7..1b0d177c0bcc 100644 --- a/kong/runloop/events.lua +++ b/kong/runloop/events.lua @@ -329,7 +329,7 @@ local function crud_ca_certificates_handler(data) local ca_id = data.entity.id local done_keys = {} - for _, entity in ipairs(certificate.CA_CERT_REFERENCE_ENTITIES) do + 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) @@ -347,6 +347,25 @@ local function crud_ca_certificates_handler(data) 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 From a30fde4947a4088ae642248952f600e7f5900090 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Fri, 10 Nov 2023 16:52:24 +0800 Subject: [PATCH 08/16] update the schema of ca_certificates --- kong/db/schema/entities/ca_certificates.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/kong/db/schema/entities/ca_certificates.lua b/kong/db/schema/entities/ca_certificates.lua index f87cd35722be..212c79dd3cc7 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, }, From 93fced6e49bc9d7dbc729a6f6d6256d0edfcae12 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Mon, 13 Nov 2023 18:12:31 +0800 Subject: [PATCH 09/16] fix: fields in schema is an array and cert_pk is a table --- kong/db/dao/ca_certificates.lua | 28 +++++----- kong/runloop/certificate.lua | 54 ++++++++++++++----- .../16-ca_certificates_routes_spec.lua | 2 +- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/kong/db/dao/ca_certificates.lua b/kong/db/dao/ca_certificates.lua index a76d77e66097..4720b3881b37 100644 --- a/kong/db/dao/ca_certificates.lua +++ b/kong/db/dao/ca_certificates.lua @@ -5,11 +5,11 @@ 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(cert_pk) +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(cert_pk, 1) + 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, cert_pk, err) + local msg = fmt("failed to select %s by ca certificate %s: %s", entity, ca_id, err) return nil, msg end @@ -18,15 +18,17 @@ function Ca_certificates:check_ca_reference(cert_pk) end end - local plugins, err = self.db.plugins:select_by_ca_certificate(cert_pk, 1, - certificate.get_ca_certificate_reference_plugins()) - if err then - local msg = fmt("failed to select plugins by ca_certificate %s: %s", cert_pk, err) - return nil, msg - 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] + if type(plugins) == "table" and #plugins > 0 then + return "plugins", plugins[1] + end end return nil, nil @@ -34,10 +36,10 @@ 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) + 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, entity, element_or_err.id) + cert_pk.id, entity, element_or_err.id) local err_t = self.errors:referenced_by_others(msg) return nil, tostring(err_t), err_t diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index 383fe2f58ef2..b87d7701672b 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -385,6 +385,17 @@ end -- here we assume the field name is always `ca_certificates` local get_ca_certificate_reference_entities do + local function is_entity_reference_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 @@ -392,8 +403,8 @@ do if not CA_CERT_REFERENCE_ENTITIES then CA_CERT_REFERENCE_ENTITIES = {} for _, entity_name in ipairs(constants.CORE_ENTITIES) do - local entity_schema = require("kong.db.schema.entities." .. entity_name) - if entity_schema.fields.ca_certificates then + local res = is_entity_reference_ca_certificates(entity_name) + if res then tb_insert(CA_CERT_REFERENCE_ENTITIES, entity_name) end end @@ -407,6 +418,30 @@ end -- here we assume the field name is always `ca_certificates` local get_ca_certificate_reference_plugins do + local function is_plugin_reference_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 @@ -414,18 +449,13 @@ do if not CA_CERT_REFERENCE_PLUGINS then CA_CERT_REFERENCE_PLUGINS = {} local loaded_plugins = kong.configuration.loaded_plugins - for name, _ in pairs(loaded_plugins) do - 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 + for name, v in pairs(loaded_plugins) do + local res, err = is_plugin_reference_ca_certificates(name) + if err then + return nil, err end - if schema.fields.config.fields.ca_certificates then + if res then CA_CERT_REFERENCE_PLUGINS[name] = true 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 f70db23271eb..fc837000895b 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 @@ -169,7 +169,7 @@ for _, strategy in helpers.each_strategy() do 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) + 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) From 242791c9d2112e5685404e47a7d6336d8b47afe2 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Tue, 14 Nov 2023 11:48:37 +0800 Subject: [PATCH 10/16] add services:select_by_ca_certificate() tests --- .../02-integration/03-db/21-services_spec.lua | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 spec/02-integration/03-db/21-services_spec.lua 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 000000000000..ec0776b4d346 --- /dev/null +++ b/spec/02-integration/03-db/21-services_spec.lua @@ -0,0 +1,225 @@ +local helpers = require "spec.helpers" +local ssl_fixtures = require "spec.fixtures.ssl" +local kong = kong + +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.all_strategies() 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", + }) + _G.kong.db = db + + 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 = kong.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 = kong.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 = kong.db.services:select_by_ca_certificate(other_ca.id) + local expected = { + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 0) + end) + + it("limits the number of returned services", function() + local services, err = kong.db.services:select_by_ca_certificate(ca1.id, 1) + local expected = { + [srv1.id] = true, + [srv2.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 1) + assert(expected[services[1].id]) + + local services, err = kong.db.services:select_by_ca_certificate(ca2.id, 1) + local expected = { + [srv3.id] = true, + [srv4.id] = true, + [srv5.id] = true, + [srv6.id] = true, + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 1) + assert(expected[services[1].id]) + + -- unreferenced ca certificate + local services, err = kong.db.services:select_by_ca_certificate(other_ca.id, 1) + local expected = { + } + local res = {} + assert.is_nil(err) + assert(services) + assert(#services == 0) + end) + end) + end) +end From 7c6bf6b255e85b17ad344fbeac1111f1eee319c0 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Tue, 14 Nov 2023 16:06:22 +0800 Subject: [PATCH 11/16] fix lint --- .../02-integration/03-db/21-services_spec.lua | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/spec/02-integration/03-db/21-services_spec.lua b/spec/02-integration/03-db/21-services_spec.lua index ec0776b4d346..0eede2e3d44d 100644 --- a/spec/02-integration/03-db/21-services_spec.lua +++ b/spec/02-integration/03-db/21-services_spec.lua @@ -1,6 +1,5 @@ local helpers = require "spec.helpers" local ssl_fixtures = require "spec.fixtures.ssl" -local kong = kong local ca_cert2 = [[ -----BEGIN CERTIFICATE----- @@ -69,7 +68,7 @@ T6nsr9aTE1yghO6GTWEPssw= -----END CERTIFICATE----- ]] -for _, strategy in helpers.all_strategies() do +for _, strategy in helpers.each_strategy() do describe("db.services #" .. strategy, function() local bp, db local ca1, ca2, other_ca @@ -80,7 +79,6 @@ for _, strategy in helpers.all_strategies() do "services", "ca_certificates", }) - _G.kong.db = db ca1 = assert(bp.ca_certificates:insert({ cert = ssl_fixtures.cert_ca, @@ -140,7 +138,7 @@ for _, strategy in helpers.all_strategies() do describe("services:select_by_ca_certificate()", function() it("selects the correct services", function() - local services, err = kong.db.services:select_by_ca_certificate(ca1.id) + local services, err = db.services:select_by_ca_certificate(ca1.id) local expected = { [srv1.id] = true, [srv2.id] = true, @@ -157,7 +155,7 @@ for _, strategy in helpers.all_strategies() do end assert.are.same(expected, res) - local services, err = kong.db.services:select_by_ca_certificate(ca2.id) + local services, err = db.services:select_by_ca_certificate(ca2.id) local expected = { [srv3.id] = true, [srv4.id] = true, @@ -175,47 +173,39 @@ for _, strategy in helpers.all_strategies() do assert.are.same(expected, res) -- unreferenced ca certificate - local services, err = kong.db.services:select_by_ca_certificate(other_ca.id) - local expected = { - } - local res = {} + 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 = kong.db.services:select_by_ca_certificate(ca1.id, 1) + 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, } - local res = {} assert.is_nil(err) assert(services) assert(#services == 1) assert(expected[services[1].id]) - local services, err = kong.db.services:select_by_ca_certificate(ca2.id, 1) + 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, } - local res = {} assert.is_nil(err) assert(services) assert(#services == 1) assert(expected[services[1].id]) -- unreferenced ca certificate - local services, err = kong.db.services:select_by_ca_certificate(other_ca.id, 1) - local expected = { - } - local res = {} + local services, err = db.services:select_by_ca_certificate(other_ca.id, 1) assert.is_nil(err) assert(services) assert(#services == 0) From a4efaa9daa112eebbea6314a9b7f1f2ebd045643 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Tue, 14 Nov 2023 17:23:01 +0800 Subject: [PATCH 12/16] add custom plugin "reference-ca-cert" and plugins:select_by_ca_certificate() tests --- kong/db/strategies/postgres/plugins.lua | 2 +- spec/02-integration/03-db/03-plugins_spec.lua | 296 +++++++++++++++++- .../plugins/reference-ca-cert/handler.lua | 6 + .../kong/plugins/reference-ca-cert/schema.lua | 15 + 4 files changed, 317 insertions(+), 2 deletions(-) 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/kong/db/strategies/postgres/plugins.lua b/kong/db/strategies/postgres/plugins.lua index 1f168ccc3c7d..6a08a4a825fb 100644 --- a/kong/db/strategies/postgres/plugins.lua +++ b/kong/db/strategies/postgres/plugins.lua @@ -16,7 +16,7 @@ function Plugins:select_by_ca_certificate(ca_id, limit, plugin_names) local name_condition = "" local escaped_names = {} if type(plugin_names) == "string" then - tb_insert(escaped_names, escape_literal(connector, plugin_names)) + 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)) diff --git a/spec/02-integration/03-db/03-plugins_spec.lua b/spec/02-integration/03-db/03-plugins_spec.lua index 474bfb15dfcd..d761b49b9dc9 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/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 000000000000..dfff3ebcbd08 --- /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 000000000000..8e388fe650a8 --- /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", }, }, + }, + }, + }, + }, +} From 3925d34d1b49412ceb51d95ff0365c95906432b9 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Wed, 15 Nov 2023 13:30:36 +0800 Subject: [PATCH 13/16] add ca_certificates:delete() tests --- kong/runloop/certificate.lua | 8 +- .../03-db/22-ca_certificates_spec.lua | 145 ++++++++++++++++++ 2 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 spec/02-integration/03-db/22-ca_certificates_spec.lua diff --git a/kong/runloop/certificate.lua b/kong/runloop/certificate.lua index b87d7701672b..f52f338ac685 100644 --- a/kong/runloop/certificate.lua +++ b/kong/runloop/certificate.lua @@ -385,7 +385,7 @@ end -- here we assume the field name is always `ca_certificates` local get_ca_certificate_reference_entities do - local function is_entity_reference_ca_certificates(name) + 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 @@ -403,7 +403,7 @@ do if not CA_CERT_REFERENCE_ENTITIES then CA_CERT_REFERENCE_ENTITIES = {} for _, entity_name in ipairs(constants.CORE_ENTITIES) do - local res = is_entity_reference_ca_certificates(entity_name) + local res = is_entity_referencing_ca_certificates(entity_name) if res then tb_insert(CA_CERT_REFERENCE_ENTITIES, entity_name) end @@ -418,7 +418,7 @@ end -- here we assume the field name is always `ca_certificates` local get_ca_certificate_reference_plugins do - local function is_plugin_reference_ca_certificates(name) + 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 @@ -450,7 +450,7 @@ do CA_CERT_REFERENCE_PLUGINS = {} local loaded_plugins = kong.configuration.loaded_plugins for name, v in pairs(loaded_plugins) do - local res, err = is_plugin_reference_ca_certificates(name) + local res, err = is_plugin_referencing_ca_certificates(name) if err then return nil, err 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 000000000000..6fd94a4c5153 --- /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 From 1c14b0e78d1e3a0b0aff6b4574b131701e2140ff Mon Sep 17 00:00:00 2001 From: Zhefeng C <38037704+catbro666@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:41:29 +0800 Subject: [PATCH 14/16] Apply suggestions from code review Co-authored-by: Michael Martin --- kong/db/errors.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kong/db/errors.lua b/kong/db/errors.lua index c66b9fc857cd..1b749a76a811 100644 --- a/kong/db/errors.lua +++ b/kong/db/errors.lua @@ -52,7 +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 - REFERENCCED_BY_OTHERS = 19, -- still referenced by other entities + REFERENCED_BY_OTHERS = 19, -- still referenced by other entities } @@ -78,7 +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.REFERENCCED_BY_OTHERS] = "referenced by others", + [ERRORS.REFERENCED_BY_OTHERS] = "referenced by others", } @@ -524,7 +524,7 @@ function _M:referenced_by_others(err) error("err must be a string", 2) end - return new_err_t(self, ERRORS.REFERENCCED_BY_OTHERS, err) + return new_err_t(self, ERRORS.REFERENCED_BY_OTHERS, err) end From 772b34c718421244d8bf16402f2c641c52614543 Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Thu, 16 Nov 2023 10:54:27 +0800 Subject: [PATCH 15/16] fix typo --- kong/api/endpoints.lua | 2 +- kong/db/errors.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kong/api/endpoints.lua b/kong/api/endpoints.lua index ff96fec1688c..eb995a357b76 100644 --- a/kong/api/endpoints.lua +++ b/kong/api/endpoints.lua @@ -35,7 +35,7 @@ local ERRORS_HTTP_CODES = { [Errors.codes.INVALID_OPTIONS] = 400, [Errors.codes.OPERATION_UNSUPPORTED] = 405, [Errors.codes.FOREIGN_KEYS_UNRESOLVED] = 400, - [Errors.codes.REFERENCCED_BY_OTHERS] = 400, + [Errors.codes.REFERENCED_BY_OTHERS] = 400, } local TAGS_AND_REGEX diff --git a/kong/db/errors.lua b/kong/db/errors.lua index 1b749a76a811..5a43911741a0 100644 --- a/kong/db/errors.lua +++ b/kong/db/errors.lua @@ -52,7 +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 + REFERENCED_BY_OTHERS = 19, -- still referenced by other entities } @@ -78,7 +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", + [ERRORS.REFERENCED_BY_OTHERS] = "referenced by others", } From 6cb08ab74d3da794493649eaeaf525cf4503edcc Mon Sep 17 00:00:00 2001 From: Zhefeng Chen Date: Fri, 17 Nov 2023 14:19:02 +0800 Subject: [PATCH 16/16] remove plugins.lua and services.lua for `off` as they're not currently being used --- kong-3.6.0-0.rockspec | 2 -- kong/db/strategies/off/plugins.lua | 46 ----------------------------- kong/db/strategies/off/services.lua | 42 -------------------------- 3 files changed, 90 deletions(-) delete mode 100644 kong/db/strategies/off/plugins.lua delete mode 100644 kong/db/strategies/off/services.lua diff --git a/kong-3.6.0-0.rockspec b/kong-3.6.0-0.rockspec index 809e11d035ac..cc233bb79807 100644 --- a/kong-3.6.0-0.rockspec +++ b/kong-3.6.0-0.rockspec @@ -247,8 +247,6 @@ build = { ["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", - ["kong.db.strategies.off.services"] = "kong/db/strategies/off/services.lua", - ["kong.db.strategies.off.plugins"] = "kong/db/strategies/off/plugins.lua", ["kong.db.migrations.state"] = "kong/db/migrations/state.lua", ["kong.db.migrations.subsystems"] = "kong/db/migrations/subsystems.lua", diff --git a/kong/db/strategies/off/plugins.lua b/kong/db/strategies/off/plugins.lua deleted file mode 100644 index 9c85d75314b7..000000000000 --- a/kong/db/strategies/off/plugins.lua +++ /dev/null @@ -1,46 +0,0 @@ -local null = ngx.null - -local Plugins = {} - -function Plugins:select_by_ca_certificate(ca_id, limit, plugin_names) - local PAGE_SIZE = 100 - local next_offset = nil - local rows, err - local matches = {} - local count = 0 - local options = { workspace = null } - - if type(plugin_names) == string then - plugin_names = { [plugin_names] = true } - end - - repeat - rows, err, next_offset = self:page(PAGE_SIZE, next_offset, options) - if err then - return nil, err - end - for _, row in ipairs(rows) do - if limit and count >= limit then - return matches, nil - end - - if (not plugin_names or plugin_names[row.name]) and - type(row.config) == 'table' and type(row.config.ca_certificates) == "table" then - for _, id in ipairs(row.config.ca_certificates) do - if id == ca_id then - table.insert(matches, row) - count = count + 1 - goto continue - end - end - end - - ::continue:: - end - - until next_offset == nil - - return matches, nil -end - -return Plugins diff --git a/kong/db/strategies/off/services.lua b/kong/db/strategies/off/services.lua deleted file mode 100644 index 41995496e8ce..000000000000 --- a/kong/db/strategies/off/services.lua +++ /dev/null @@ -1,42 +0,0 @@ -local null = ngx.null -local tb_insert = table.insert - -local Services = {} - -function Services:select_by_ca_certificate(ca_id, limit) - local PAGE_SIZE = 100 - local next_offset = nil - local rows, err - local matches = {} - local count = 0 - local options = { workspace = null } - - repeat - rows, err, next_offset = self:page(PAGE_SIZE, next_offset, options) - if err then - return nil, err - end - for _, row in ipairs(rows) do - if limit and count >= limit then - return matches, nil - end - - if type(row.ca_certificates) == 'table' then - for _, id in ipairs(row.ca_certificates) do - if id == ca_id then - tb_insert(matches, row) - count = count + 1 - goto continue - end - end - end - - ::continue:: - end - - until next_offset == nil - - return matches, nil -end - -return Services