From 25c7205d40183a5fbdc5adc0e3558248d1bb5288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Garc=C3=ADa=20Cota?= Date: Sat, 17 Mar 2018 01:58:06 +0100 Subject: [PATCH] feat(db) move ssl_cert schema to new db --- kong-0.13.0-0.rockspec | 7 +- kong/api/endpoints.lua | 2 + kong/api/init.lua | 3 +- kong/api/routes/certificates.lua | 345 +---- kong/api/routes/snis.lua | 53 - kong/core/certificate.lua | 36 +- kong/core/handler.lua | 26 +- kong/dao/factory.lua | 2 - kong/dao/migrations/cassandra.lua | 116 +- kong/dao/migrations/helpers.lua | 212 +++ kong/dao/migrations/postgres.lua | 78 +- kong/dao/schemas/ssl_certificates.lua | 15 - kong/dao/schemas/ssl_servers_names.lua | 14 - kong/db/dao/certificates.lua | 209 +++ kong/db/dao/init.lua | 25 +- kong/db/dao/server_names.lua | 159 ++ kong/db/errors.lua | 38 +- kong/db/init.lua | 9 +- kong/db/schema/entities/certificates.lua | 15 + kong/db/schema/entities/server_names.lua | 15 + .../06-certificates_routes_spec.lua | 1274 ----------------- .../02-integration/05-proxy/05-ssl_spec.lua | 2 +- .../02-core_entities_invalidations_spec.lua | 20 +- .../000-new-dao/02-db_core_entities_spec.lua | 14 +- .../01-helpers/01-blueprints_spec.lua | 14 +- .../03-dao/04-constraints_spec.lua | 4 +- .../06-certificates_routes_spec.lua | 1016 +++---------- spec/02-integration/05-proxy/05-ssl_spec.lua | 2 +- .../02-core_entities_invalidations_spec.lua | 57 +- spec/fixtures/blueprints.lua | 9 +- 30 files changed, 1176 insertions(+), 2615 deletions(-) delete mode 100644 kong/api/routes/snis.lua delete mode 100644 kong/dao/schemas/ssl_certificates.lua delete mode 100644 kong/dao/schemas/ssl_servers_names.lua create mode 100644 kong/db/dao/certificates.lua create mode 100644 kong/db/dao/server_names.lua create mode 100644 kong/db/schema/entities/certificates.lua create mode 100644 kong/db/schema/entities/server_names.lua delete mode 100644 spec-old-api/02-integration/04-admin_api/06-certificates_routes_spec.lua diff --git a/kong-0.13.0-0.rockspec b/kong-0.13.0-0.rockspec index e4561be2c61b..dce4d6c1036c 100644 --- a/kong-0.13.0-0.rockspec +++ b/kong-0.13.0-0.rockspec @@ -86,7 +86,6 @@ build = { ["kong.api.routes.cache"] = "kong/api/routes/cache.lua", ["kong.api.routes.upstreams"] = "kong/api/routes/upstreams.lua", ["kong.api.routes.certificates"] = "kong/api/routes/certificates.lua", - ["kong.api.routes.snis"] = "kong/api/routes/snis.lua", ["kong.tools.ip"] = "kong/tools/ip.lua", ["kong.tools.ciphers"] = "kong/tools/ciphers.lua", @@ -114,8 +113,6 @@ build = { ["kong.dao.schemas.plugins"] = "kong/dao/schemas/plugins.lua", ["kong.dao.schemas.upstreams"] = "kong/dao/schemas/upstreams.lua", ["kong.dao.schemas.targets"] = "kong/dao/schemas/targets.lua", - ["kong.dao.schemas.ssl_certificates"] = "kong/dao/schemas/ssl_certificates.lua", - ["kong.dao.schemas.ssl_servers_names"] = "kong/dao/schemas/ssl_servers_names.lua", ["kong.dao.db"] = "kong/dao/db/init.lua", ["kong.dao.db.cassandra"] = "kong/dao/db/cassandra.lua", ["kong.dao.db.postgres"] = "kong/dao/db/postgres.lua", @@ -129,9 +126,13 @@ build = { ["kong.db"] = "kong/db/init.lua", ["kong.db.errors"] = "kong/db/errors.lua", ["kong.db.dao"] = "kong/db/dao/init.lua", + ["kong.db.dao.certificates"] = "kong/db/dao/certificates.lua", + ["kong.db.dao.server_names"] = "kong/db/dao/server_names.lua", ["kong.db.schema"] = "kong/db/schema/init.lua", ["kong.db.schema.entities.routes"] = "kong/db/schema/entities/routes.lua", ["kong.db.schema.entities.services"] = "kong/db/schema/entities/services.lua", + ["kong.db.schema.entities.certificates"] = "kong/db/schema/entities/certificates.lua", + ["kong.db.schema.entities.server_names"] = "kong/db/schema/entities/server_names.lua", ["kong.db.schema.entity"] = "kong/db/schema/entity.lua", ["kong.db.schema.metaschema"] = "kong/db/schema/metaschema.lua", ["kong.db.schema.typedefs"] = "kong/db/schema/typedefs.lua", diff --git a/kong/api/endpoints.lua b/kong/api/endpoints.lua index 080e8cfad1d4..0f056e79a3b8 100644 --- a/kong/api/endpoints.lua +++ b/kong/api/endpoints.lua @@ -20,6 +20,8 @@ local ERRORS_HTTP_CODES = { [Errors.codes.NOT_FOUND] = 404, [Errors.codes.INVALID_OFFSET] = 400, [Errors.codes.DATABASE_ERROR] = 500, + [Errors.codes.CONFLICTING_INPUT] = 409, + [Errors.codes.INVALID_INPUT] = 400, } diff --git a/kong/api/init.lua b/kong/api/init.lua index f32a93085e11..fa96b4ca4319 100644 --- a/kong/api/init.lua +++ b/kong/api/init.lua @@ -212,8 +212,7 @@ ngx.log(ngx.DEBUG, "Loading Admin API endpoints") -- Load core routes -for _, v in ipairs({"kong", "apis", "consumers", "plugins", "cache", - "certificates", "snis", "upstreams"}) do +for _, v in ipairs({"kong", "apis", "consumers", "plugins", "cache", "upstreams"}) do local routes = require("kong.api.routes." .. v) attach_routes(routes) end diff --git a/kong/api/routes/certificates.lua b/kong/api/routes/certificates.lua index 0f8e1bbb4442..08ee34f71193 100644 --- a/kong/api/routes/certificates.lua +++ b/kong/api/routes/certificates.lua @@ -1,317 +1,80 @@ -local crud = require "kong.api.crud_helpers" -local utils = require "kong.tools.utils" -local cjson = require "cjson" +local endpoints = require "kong.api.endpoints" +local utils = require "kong.tools.utils" +local function get_cert_by_server_name_or_id(self, db, helpers) + local id = self.params.certificates -local function create_certificate(self, dao_factory, helpers) - local snis - if type(self.params.snis) == "string" then - snis = utils.split(self.params.snis, ",") - end - - self.params.snis = nil - - if snis then - -- dont add the certificate or any snis if we have an SNI conflict - -- its fairly inefficient that we have to loop twice over the datastore - -- but no support for OR queries means we gotsta! - local snis_in_request = {} - - for _, sni in ipairs(snis) do - if snis_in_request[sni] then - return helpers.responses.send_HTTP_CONFLICT("duplicate SNI in " .. - "request: " .. sni) - end - - local cnt, err = dao_factory.ssl_servers_names:count { - name = sni, - } - if err then - return helpers.yield_error(err) - end - - if cnt > 0 then - -- Note: it could be that the SNI is not associated with any - -- certificate, but we don't handle this case. (for PostgreSQL - -- only, as C* requires a cert_id for its partition key). - return helpers.responses.send_HTTP_CONFLICT("SNI already exists: " .. - sni) - end - - snis_in_request[sni] = true - end - end - - local ssl_cert, err = dao_factory.ssl_certificates:insert(self.params) - if err then - return helpers.yield_error(err) - end - - ssl_cert.snis = setmetatable({}, cjson.empty_array_mt) - - -- insert SNIs if given - - if snis then - for i, sni in ipairs(snis) do - local ssl_server_name = { - name = sni, - ssl_certificate_id = ssl_cert.id, - } - - local row, err = dao_factory.ssl_servers_names:insert(ssl_server_name) - if err then - return helpers.yield_error(err) - end - - ssl_cert.snis[i] = row.name - end - end - - return helpers.responses.send_HTTP_CREATED(ssl_cert) -end - - -local function update_certificate(self, dao_factory, helpers) - -- check if exists - local ssl_cert, err = dao_factory.ssl_certificates:find { - id = self.params.id - } - if err then - return helpers.yield_error(err) - end - - if not ssl_cert then - return helpers.responses.send_HTTP_NOT_FOUND() - end - - local snis - if type(self.params.snis) == "string" then - snis = utils.split(self.params.snis, ",") - - elseif self.params.snis == ngx.null then - snis = {} - end - - self.params.snis = nil - - local snis_in_request = {} -- check for duplicate snis in the request - local snis_in_db = {} -- avoid db insert if sni is already present in db - - -- if snis field present - -- 1. no duplicate snis should be present in the request - -- 2. check if any sni in the request is using a cert - -- other than the one being updated - - if snis then - for _, sni in ipairs(snis) do - if snis_in_request[sni] then - return helpers.responses.send_HTTP_CONFLICT("duplicate SNI in " .. - "request: " .. sni) - end - - local sni_in_db, err = dao_factory.ssl_servers_names:find { - name = sni, - } - if err then - return helpers.yield_error(err) - end - - if sni_in_db then - if sni_in_db.ssl_certificate_id ~= ssl_cert.id then - return helpers.responses.send_HTTP_CONFLICT( - "SNI '" .. sni .. "' already associated with existing " .. - "certificate (" .. sni_in_db.ssl_certificate_id .. ")" - ) - end - - snis_in_db[sni] = true - end - - snis_in_request[sni] = true - end - end - - local old_snis, err = dao_factory.ssl_servers_names:find_all { - ssl_certificate_id = ssl_cert.id, - } - if err then - return helpers.yield_error(err) - end - - -- update certificate if necessary - if self.params.key or self.params.cert then - self.params.created_at = ssl_cert.created_at - - ssl_cert, err = dao_factory.ssl_certificates:update(self.params, { - id = self.params.id, - }, { full = true }) - if err then - return helpers.yield_error(err) + if not utils.is_valid_uuid(id) then + local cert, _, err_t = db.certificates:select_by_server_name(id) + if err_t then + return endpoints.handle_error(err_t) end - end - - ssl_cert.snis = setmetatable({}, cjson.empty_array_mt) - - if not snis then - for i = 1, #old_snis do - ssl_cert.snis[i] = old_snis[i].name - end - - return helpers.responses.send_HTTP_OK(ssl_cert) - end - - -- insert/delete SNIs into db if snis field was present in the request - for i, sni in ipairs(snis) do - if not snis_in_db[sni] then - local ssl_server_name = { - name = sni, - ssl_certificate_id = ssl_cert.id, - } - - local _, err = dao_factory.ssl_servers_names:insert(ssl_server_name) - if err then - return helpers.yield_error(err) - end - end - - ssl_cert.snis[i] = sni - end - -- delete snis which should no longer use ssl_cert - for i = 1, #old_snis do - if not snis_in_request[old_snis[i].name] then - -- ignoring error - -- if we want to return an error here - -- to return 4xx here, the current transaction needs to be - -- rolled back else we risk an invalid state and confusing - -- the user - dao_factory.ssl_servers_names:delete({ - name = old_snis[i].name, - }) - end + self.params.certificates = cert.id end - - return helpers.responses.send_HTTP_OK(ssl_cert) end return { - ["/certificates/"] = { - POST = function(self, dao_factory, helpers) - create_certificate(self, dao_factory, helpers) + ["/certificates"] = { + -- override to include the server_names list when getting all certificates + GET = function(self, db, helpers) + local data, _, err_t, offset = + db.certificates:page_with_name_list(self.args.size, + self.args.offset) + if not data then + return endpoints.handle_error(err_t) + end + + local next_page = offset and string.format("/certificates?offset=%s", + ngx.escape_uri(offset)) or ngx.null + + return helpers.responses.send_HTTP_OK { + data = data, + offset = offset, + next = next_page, + } end, - - GET = function(self, dao_factory, helpers) - local ssl_certificates, err = dao_factory.ssl_certificates:find_all() - if err then - return helpers.yield_error(err) + -- override to accept the server_names param when creating a certificate + POST = function(self, db, helpers) + local data, _, err_t = db.certificates:insert_with_name_list(self.args.post) + if err_t then + return endpoints.handle_error(err_t) end - for i = 1, #ssl_certificates do - local rows, err = dao_factory.ssl_servers_names:find_all { - ssl_certificate_id = ssl_certificates[i].id - } - if err then - return helpers.yield_error(err) - end - - ssl_certificates[i].snis = setmetatable({}, cjson.empty_array_mt) - - for j = 1, #rows do - ssl_certificates[i].snis[j] = rows[j].name - end - end - - return helpers.responses.send_HTTP_OK({ - data = #ssl_certificates > 0 and ssl_certificates or cjson.empty_array, - total = #ssl_certificates, - }) - end, - - - PUT = function(self, dao_factory, helpers) - -- no id present, behaviour should be same as POST - if not self.params.id then - create_certificate(self, dao_factory, helpers) - return -- avoid tail call - end - - -- id present in body - update_certificate(self, dao_factory, helpers) + return helpers.responses.send_HTTP_CREATED(data) end, }, + ["/certificates/:certificates"] = { + before = get_cert_by_server_name_or_id, - ["/certificates/:sni_or_uuid"] = { - before = function(self, dao_factory, helpers) - if utils.is_valid_uuid(self.params.sni_or_uuid) then - self.ssl_certificate_id = self.params.sni_or_uuid - - else - -- get requested SNI - - local row, err = dao_factory.ssl_servers_names:find { - name = self.params.sni_or_uuid - } - if err then - return helpers.yield_error(err) - end - - if not row then - return helpers.responses.send_HTTP_NOT_FOUND() - end + -- override to include the server_names list when getting an individual certificate + GET = function(self, db, helpers) + local pk = { id = self.params.certificates } - -- cache certificate row id - - self.ssl_certificate_id = row.ssl_certificate_id + local cert, _, err_t = db.certificates:select_with_name_list(pk) + if err_t then + return endpoints.handle_error(err_t) end - self.params.sni_or_uuid = nil + return helpers.responses.send_HTTP_OK(cert) end, - - GET = function(self, dao_factory, helpers) - local row, err = dao_factory.ssl_certificates:find { - id = self.ssl_certificate_id - } - if err then - return helpers.yield_error(err) + -- override to accept the server_names param when updating a certificate + PATCH = function(self, db, helpers) + local pk = { id = self.params.certificates } + local cert, _, err_t = db.certificates:update_with_name_list(pk, self.args.post) + if err_t then + return endpoints.handle_error(err_t) end - - if not row then - return helpers.responses.send_HTTP_NOT_FOUND() - end - - -- add list of other SNIs for this certificate - - row.snis = setmetatable({}, cjson.empty_array_mt) - - local rows, err = dao_factory.ssl_servers_names:find_all { - ssl_certificate_id = self.ssl_certificate_id - } - if err then - return helpers.yield_error(err) - end - - for i = 1, #rows do - row.snis[i] = rows[i].name - end - - return helpers.responses.send_HTTP_OK(row) - end, - - - PATCH = function(self, dao_factory, helpers) - self.params.id = self.ssl_certificate_id - update_certificate(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(cert) end, + }, - - DELETE = function(self, dao_factory, helpers) - return crud.delete({ - id = self.ssl_certificate_id - }, dao_factory.ssl_certificates) - end, - } + ["/certificates/:certificates/server_names"] = { + before = get_cert_by_server_name_or_id, + }, } + diff --git a/kong/api/routes/snis.lua b/kong/api/routes/snis.lua deleted file mode 100644 index cce7e66ab961..000000000000 --- a/kong/api/routes/snis.lua +++ /dev/null @@ -1,53 +0,0 @@ -local crud = require "kong.api.crud_helpers" - - -return { - ["/snis/"] = { - GET = function(self, dao_factory) - crud.paginated_set(self, dao_factory.ssl_servers_names) - end, - - - PUT = function(self, dao_factory) - crud.put(self.params, dao_factory.ssl_servers_names) - end, - - - POST = function(self, dao_factory) - crud.post(self.params, dao_factory.ssl_servers_names) - end, - }, - - - ["/snis/:name"] = { - before = function(self, dao_factory, helpers) - local row, err = dao_factory.ssl_servers_names:find { - name = self.params.name - } - if err then - return helpers.yield_error(err) - end - - if not row then - return helpers.responses.send_HTTP_NOT_FOUND() - end - - self.sni = row - end, - - - GET = function(self, dao_factory, helpers) - return helpers.responses.send_HTTP_OK(self.sni) - end, - - - PATCH = function(self, dao_factory) - crud.patch(self.params, dao_factory.ssl_servers_names, self.sni) - end, - - - DELETE = function(self, dao_factory) - crud.delete(self.sni, dao_factory.ssl_servers_names) - end, - } -} diff --git a/kong/core/certificate.lua b/kong/core/certificate.lua index f087fd067066..8bae5af669ea 100644 --- a/kong/core/certificate.lua +++ b/kong/core/certificate.lua @@ -15,50 +15,46 @@ end local _M = {} -local function find_certificate(sni) - local row, err = singletons.dao.ssl_servers_names:find { - name = sni - } +local function find_certificate(sn) + local row, err = singletons.db.server_names:select_by_name(sn) if err then return nil, err end if not row then - log(DEBUG, "no server name registered for client-provided SNI: '", - sni, "'") + log(DEBUG, "no server name registered for client-provided name: '", + sn, "'") return true end - -- fetch SSL certificate for this SNI + -- fetch SSL certificate for this server name - local ssl_certificate, err = singletons.dao.ssl_certificates:find { - id = row.ssl_certificate_id - } + local certificate, err = singletons.db.certificates:select(row.certificate) if err then return nil, err end - if not ssl_certificate then - return nil, "no SSL certificate configured for server name: " .. sni + if not certificate then + return nil, "no SSL certificate configured for server name: " .. sn end return { - cert = ssl_certificate.cert, - key = ssl_certificate.key, + cert = certificate.cert, + key = certificate.key, } end function _M.execute() - -- retrieve SNI or raw server IP + -- retrieve server name or raw server IP - local sni, err = ssl.server_name() + local sn, err = ssl.server_name() if err then log(ERR, "could not retrieve Server Name Indication: ", err) return ngx.exit(ngx.ERROR) end - if not sni then + if not sn then log(DEBUG, "no Server Name Indication provided by client, serving ", "default proxy SSL certificate") -- use fallback certificate @@ -66,11 +62,11 @@ function _M.execute() end local lru = singletons.cache.mlcache.lru - local pem_cache_key = "pem_ssl_certificates:" .. sni - local parsed_cache_key = "parsed_ssl_certificates:" .. sni + local pem_cache_key = "pem_ssl_certificates:" .. sn + local parsed_cache_key = "parsed_ssl_certificates:" .. sn local pem_cert_and_key, err = singletons.cache:get(pem_cache_key, nil, - find_certificate, sni) + find_certificate, sn) if not pem_cert_and_key then log(ERR, err) return ngx.exit(ngx.ERROR) diff --git a/kong/core/handler.lua b/kong/core/handler.lua index 46808124910b..d0d7d1f2b7e6 100644 --- a/kong/core/handler.lua +++ b/kong/core/handler.lua @@ -152,7 +152,7 @@ return { reports.init_worker() -- initialize local local_events hooks - + local db = singletons.db local dao = singletons.dao local cache = singletons.cache local worker_events = singletons.worker_events @@ -253,32 +253,32 @@ return { worker_events.register(function(data) log(DEBUG, "[events] SNI updated, invalidating cached certificates") - local sni = data.entity + local sn = data.entity - cache:invalidate("pem_ssl_certificates:" .. sni.name) - cache:invalidate("parsed_ssl_certificates:" .. sni.name) - end, "crud", "ssl_servers_names") + cache:invalidate("pem_ssl_certificates:" .. sn.name) + cache:invalidate("parsed_ssl_certificates:" .. sn.name) + end, "crud", "server_names") worker_events.register(function(data) log(DEBUG, "[events] SSL cert updated, invalidating cached certificates") local certificate = data.entity - local rows, err = dao.ssl_servers_names:find_all { - ssl_certificate_id = certificate.id - } + local rows, err = db.server_names:for_certificate({ + id = certificate.id + }) if not rows then - log(ERR, "[events] could not find associated SNIs for certificate: ", + log(ERR, "[events] could not find associated server names for certificate: ", err) end for i = 1, #rows do - local sni = rows[i] + local sn = rows[i] - cache:invalidate("pem_ssl_certificates:" .. sni.name) - cache:invalidate("parsed_ssl_certificates:" .. sni.name) + cache:invalidate("pem_ssl_certificates:" .. sn.name) + cache:invalidate("parsed_ssl_certificates:" .. sn.name) end - end, "crud", "ssl_certificates") + end, "crud", "certificates") -- target updates diff --git a/kong/dao/factory.lua b/kong/dao/factory.lua index 67e4902d2536..1a6185266f03 100644 --- a/kong/dao/factory.lua +++ b/kong/dao/factory.lua @@ -6,8 +6,6 @@ local CORE_MODELS = { "apis", "consumers", "plugins", - "ssl_certificates", - "ssl_servers_names", "upstreams", "targets", } diff --git a/kong/dao/migrations/cassandra.lua b/kong/dao/migrations/cassandra.lua index 6c86592e6e31..e5fea85e86a5 100644 --- a/kong/dao/migrations/cassandra.lua +++ b/kong/dao/migrations/cassandra.lua @@ -1,5 +1,8 @@ local log = require "kong.cmd.utils.log" +local cassandra = require "cassandra" +local utils = require "kong.tools.utils" +local migration_helpers = require "kong.dao.migrations.helpers" return { { @@ -240,7 +243,7 @@ return { { name = "2016-12-14-172100_move_ssl_certs_to_core", up = [[ - CREATE TABLE ssl_certificates( + CREATE TABLE IF NOT EXISTS ssl_certificates( id uuid PRIMARY KEY, cert text, key text , @@ -628,5 +631,116 @@ return { CREATE INDEX IF NOT EXISTS ON targets(target); ]], down = nil + }, + { + name = "2018-03-22-141700_create_new_ssl_tables", + up = [[ + CREATE TABLE IF NOT EXISTS certificates( + partition text, + id uuid, + cert text, + key text, + created_at timestamp, + PRIMARY KEY (partition, id) + ); + + CREATE TABLE IF NOT EXISTS server_names( + partition text, + id uuid, + name text, + certificate_id uuid, + created_at timestamp, + PRIMARY KEY (partition, id) + ); + + CREATE INDEX IF NOT EXISTS server_names_name_idx ON server_names(name); + CREATE INDEX IF NOT EXISTS server_names_certificate_id_idx ON server_names(certificate_id); + ]], + down = nil + }, + + { name = "2018-03-26-234600_copy_records_to_new_ssl_tables", + up = function(_, _, dao) + local ssl_certificates_def = { + name = "ssl_certificates", + columns = { + id = "uuid", + cert = "text", + key = "text", + created_at = "timestamp", + }, + partition_keys = { "id" }, + } + + local certificates_def = { + name = "certificates", + columns = { + partition = "text", + id = "uuid", + cert = "text", + key = "text", + created_at = "timestamp", + }, + partition_keys = { "partition", "id" }, + } + + local _, err = migration_helpers.cassandra.copy_records(dao, + ssl_certificates_def, + certificates_def, { + partition = function() return cassandra.text("certificates") end, + id = "id", + cert = "cert", + key = "key", + created_at = "created_at", + }) + if err then + return err + end + + local ssl_servers_names_def = { + name = "ssl_servers_names", + columns = { + name = "text", + ssl_certificate_id = "uuid", + created_at = "timestamp", + }, + partition_keys = { "name", "ssl_certificate_id" }, + } + + local server_names_def = { + name = "server_names", + columns = { + partition = "text", + id = "uuid", + name = "text", + certificate_id = "uuid", + created_at = "timestamp", + }, + partition_keys = { "partition", "id" }, + } + + local _, err = migration_helpers.cassandra.copy_records(dao, + ssl_servers_names_def, + server_names_def, { + partition = function() return cassandra.text("server_names") end, + id = function() return cassandra.uuid(utils.uuid(3)) end, + name = "name", + certificate_id = "ssl_certificate_id", + created_at = "created_at", + }) + if err then + return err + end + end, + down = nil + }, + { name = "2018-03-27-002500_drop_old_ssl_tables", + up = [[ + DROP INDEX ssl_servers_names_ssl_certificate_id_idx; + DROP TABLE ssl_certificates; + DROP TABLE ssl_servers_names; + ]], + down = nil } + } diff --git a/kong/dao/migrations/helpers.lua b/kong/dao/migrations/helpers.lua index 9010e887aa1d..b34c6fd90468 100644 --- a/kong/dao/migrations/helpers.lua +++ b/kong/dao/migrations/helpers.lua @@ -1,8 +1,12 @@ local json_decode = require("cjson.safe").decode +local cassandra = require("cassandra") +local utils = require "kong.tools.utils" local _M = {} +local fmt = string.format +local table_concat = table.concat -- Iterator to update plugin configurations. -- It works indepedent of the underlying datastore. @@ -95,5 +99,213 @@ function _M.plugin_config_iterator(dao, plugin_name) end end +_M.cassandra = {} + +local CASSANDRA_EXECUTE_OPTS = { consistency = cassandra.consistencies.all } + +--[[ +Copy all records from the table defined by source_table_def into +destination_table_def. Both table_defs have the following structure + { name = "ssl_certificates", + columns = { + id = "uuid", + cert = "text", + key = "text", + created_at = "timestamp", + }, + partition_keys = { "id" }, + } + +columns_to_copy is a hash-like table. +* Each key must be a string D representing a column in the destination table. +* If the value is a string S, then the value of the S column in the source + table will be assigned to the D column in destination. +* If the value is a function, then the result of executing it will be assigned + to the D column. +Example: + { + partition = function() return cassandra.text("certificates") end, + id = "id", + cert = "cert", + key = "key", + created_at = "created_at", + } + +The function takes the "source row" as parameter, so it could be used to do things +like merging two fields together into one, or putting a string in uppercase. +--]] +function _M.cassandra.copy_records(dao, + source_table_def, + destination_table_def, + columns_to_copy) + + local coordinator, err = dao.db:get_coordinator() + if not coordinator then + return nil, err + end + + local cql = fmt("SELECT * FROM %s", source_table_def.name) + for rows, err in coordinator:iterate(cql) do + if err then + return nil, err + end + + for _, source_row in ipairs(rows) do + local column_names = {} + local values = {} + local len = 0 + + for dest_column_name, source_value in pairs(columns_to_copy) do + if type(source_value) == "string" then + source_value = source_row[source_value] + local dest_type = destination_table_def.columns[dest_column_name] + local type_converter = cassandra[dest_type] + if not type_converter then + return nil, fmt("Could not find the cassandra type converter for column %s (type %s)", + dest_column_name, source_table_def[dest_column_name]) + end + source_value = type_converter(source_value) + + elseif type(source_value) == "function" then + source_value = source_value(source_row) + else + return nil, fmt("Expected a string or function, found %s (a %s)", + tostring(source_value), type(source_value)) + end + + if source_value ~= nil then + len = len + 1 + values[len] = source_value + column_names[len] = dest_column_name + end + end + + local question_marks = string.sub(string.rep("?, ", len), 1, -3) + + local insert_cql = fmt("INSERT INTO %s (%s) VALUES (%s)", + destination_table_def.name, + table_concat(column_names, ", "), + question_marks) + local _, err = coordinator:execute(insert_cql, values, CASSANDRA_EXECUTE_OPTS) + if err then + return nil, err + end + end + end +end + +do + + local function create_table_if_not_exists(coordinator, table_def) + + local partition_keys = table_def.partition_keys + local primary_key_cql = "" + if #partition_keys > 0 then + primary_key_cql = fmt(", PRIMARY KEY (%s)", table_concat(partition_keys, ", ")) + end + + local column_declarations = {} + local len = 0 + for name, typ in pairs(table_def.columns) do + len = len + 1 + column_declarations[len] = fmt("%s %s", name, typ) + end + local column_declarations_cql = table_concat(column_declarations, ", ") + + local cql = fmt("CREATE TABLE IF NOT EXISTS %s(%s%s);", + table_def.name, + column_declarations_cql, + primary_key_cql) + return coordinator:execute(cql, {}, CASSANDRA_EXECUTE_OPTS) + end + + + local function drop_table_if_exists(coordinator, table_name) + local cql = fmt("DROP TABLE IF EXISTS %s;", table_name) + return coordinator:execute(cql, {}, CASSANDRA_EXECUTE_OPTS) + end + + + local function get_columns_to_copy(table_structure) + local res = {} + for k, _ in pairs(table_structure.columns) do + res[k] = k + end + return res + end + + + local function create_aux_table_def(table_def) + local aux_table_def = utils.deep_copy(table_def) + aux_table_def.name = "copy_of_" .. table_def.name + aux_table_def.columns.partition = "text" + table.insert(aux_table_def.partition_keys, 1, "partition") + return aux_table_def + end + + --[[ + Add a new partition key called "partition" to the table specified by table_def. + + table_def has the following structure: + { name = "ssl_certificates", + columns = { + id = "uuid", + cert = "text", + key = "text", + created_at = "timestamp", + }, + partition_keys = { "id" }, + } + --]] + + function _M.cassandra.add_partition(dao, table_def) + + local coordinator, err = dao.db:get_coordinator() + if not coordinator then + return nil, err + end + + table_def = utils.deep_copy(table_def) + + local aux_table_def = create_aux_table_def(table_def) + local columns_to_copy = get_columns_to_copy(table_def) + columns_to_copy.partition = function() return cassandra.text(table_def.name) end + + local _, err = create_table_if_not_exists(coordinator, aux_table_def) + if err then + return nil, err + end + + local _, err = _M.copy_records(dao, table_def, aux_table_def, columns_to_copy) + if err then + return nil, err + end + + local _, err = drop_table_if_exists(coordinator, table_def.name) + if err then + return nil, err + end + + table_def.columns.partition = "text" + table.insert(table_def.partition_keys, 1, "partition") + + local _, err = create_table_if_not_exists(coordinator, table_def) + if err then + return nil, err + end + + local _, err = _M.copy_records(dao, aux_table_def, table_def, columns_to_copy) + if err then + return nil, err + end + + local _, err = drop_table_if_exists(coordinator, aux_table_def.name) + if err then + return nil, err + end + end + +end + return _M diff --git a/kong/dao/migrations/postgres.lua b/kong/dao/migrations/postgres.lua index 6ec70a85308b..c697897b0ce8 100644 --- a/kong/dao/migrations/postgres.lua +++ b/kong/dao/migrations/postgres.lua @@ -1,3 +1,5 @@ +local utils = require "kong.tools.utils" + return { { name = "2015-01-12-175310_skeleton", @@ -199,14 +201,14 @@ return { { name = "2016-12-14-172100_move_ssl_certs_to_core", up = [[ - CREATE TABLE ssl_certificates( + CREATE TABLE IF NOT EXISTS ssl_certificates( id uuid PRIMARY KEY, cert text , key text , created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc') ); - CREATE TABLE ssl_servers_names( + CREATE TABLE IF NOT EXISTS ssl_servers_names( name text PRIMARY KEY, ssl_certificate_id uuid REFERENCES ssl_certificates(id) ON DELETE CASCADE, created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc') @@ -692,4 +694,76 @@ return { ]], down = nil }, + { + name = "2018-03-27-123400_prepare_certs_and_server_names", + up = [[ + DO $$ + BEGIN + ALTER TABLE ssl_certificates RENAME TO certificates; + ALTER TABLE ssl_servers_names RENAME TO server_names; + EXCEPTION WHEN duplicate_table THEN + -- Do nothing, accept existing state + END$$; + + DO $$ + BEGIN + ALTER TABLE server_names RENAME COLUMN ssl_certificate_id TO certificate_id; + ALTER TABLE server_names ADD COLUMN id uuid; + EXCEPTION WHEN undefined_column THEN + -- Do nothing, accept existing state + END$$; + ]], + down = nil + }, + { + name = "2018-03-27-125400_fill_in_server_names_ids", + up = function(_, _, dao) + local rows, err = dao.db:query([[ + SELECT * FROM server_names; + ]]) + if err then + return err + end + + local fmt = string.format + + for _, row in ipairs(rows) do + local sql = fmt("UPDATE server_names SET id = '%s' WHERE name = '%s';", + utils.uuid(), + row.name) + local _, err = dao.db:query(sql) + if err then + return err + end + end + end, + down = nil + }, + { + name = "2018-03-27-130400_make_ids_primary_keys_in_server_names", + up = [[ + ALTER TABLE server_names + DROP CONSTRAINT IF EXISTS ssl_servers_names_pkey; + + ALTER TABLE server_names + DROP CONSTRAINT IF EXISTS ssl_server_names_ssl_certificate_id_fkey; + + DO $$ + BEGIN + ALTER TABLE server_names + ADD CONSTRAINT server_names_name_unique UNIQUE(name); + + ALTER TABLE server_names + ADD PRIMARY KEY (id); + + ALTER TABLE server_names + ADD CONSTRAINT server_names_certificate_id_fkey + FOREIGN KEY (certificate_id) + REFERENCES certificates; + EXCEPTION WHEN duplicate_table THEN + -- Do nothing, accept existing state + END$$; + ]], + down = nil + }, } diff --git a/kong/dao/schemas/ssl_certificates.lua b/kong/dao/schemas/ssl_certificates.lua deleted file mode 100644 index 4b87c092177f..000000000000 --- a/kong/dao/schemas/ssl_certificates.lua +++ /dev/null @@ -1,15 +0,0 @@ -return { - table = "ssl_certificates", - primary_key = { "id" }, - fields = { - id = { type = "id", dao_insert_value = true, required = true }, - cert = { type = "string", required = true }, - key = { type = "string", required = true }, - created_at = { - type = "timestamp", - immutable = true, - dao_insert_value = true, - required = true, - }, - }, -} diff --git a/kong/dao/schemas/ssl_servers_names.lua b/kong/dao/schemas/ssl_servers_names.lua deleted file mode 100644 index f3b6c7e636ab..000000000000 --- a/kong/dao/schemas/ssl_servers_names.lua +++ /dev/null @@ -1,14 +0,0 @@ -return { - table = "ssl_servers_names", - primary_key = { "name" }, - fields = { - name = { type = "text", required = true, unique = true }, - ssl_certificate_id = { type = "id", foreign = "ssl_certificates:id" }, - created_at = { - type = "timestamp", - immutable = true, - dao_insert_value = true, - required = true, - }, - }, -} diff --git a/kong/db/dao/certificates.lua b/kong/db/dao/certificates.lua new file mode 100644 index 000000000000..e7ab877304a4 --- /dev/null +++ b/kong/db/dao/certificates.lua @@ -0,0 +1,209 @@ +local singletons = require "kong.singletons" +local cjson = require "cjson" +local utils = require "kong.tools.utils" + +-- Get an array of server names from either a string (split+sort), +-- an array(sort) or ngx.null(return {}) +-- Returns an error if the list has duplicates +-- Returns nil if input is falsy. +local function parse_name_list(input, errors) + local name_list + if type(input) == "string" then + name_list = utils.split(input, ",") + elseif type(input) == "table" then + name_list = utils.shallow_copy(input) + elseif input == ngx.null then + name_list = {} + end + + if not name_list then + return nil + end + + local found = {} + for _, name in ipairs(name_list) do + if found[name] then + local msg = "duplicate server name in request: " .. name + local err_t = errors:invalid_input(msg) + return nil, tostring(err_t), err_t + end + found[name] = true + end + + table.sort(name_list) + return setmetatable(name_list, cjson.empty_array_mt) +end + + +local _Certificates = {} + +-- Creates a certificate +-- If the provided cert has a field called "server_names" it will be used to generate server +-- names associated to the cert, after being parsed by parse_name_list. +-- Returns a certificate with the server_names sorted alphabetically. +function _Certificates:insert_with_name_list(cert) + local db = singletons.db + local name_list, err, err_t = parse_name_list(cert.server_names, self.errors) + if err then + return nil, err, err_t + end + + if name_list then + local ok, err, err_t = db.server_names:check_list_is_new(name_list) + if not ok then + return nil, err, err_t + end + end + + cert.server_names = nil + cert, err, err_t = assert(self:insert(cert)) + if not cert then + return nil, err, err_t + end + cert.server_names = name_list or cjson.empty_array + + if name_list then + local ok, err, err_t = db.server_names:insert_list({id = cert.id}, name_list) + if not ok then + return nil, err, err_t + end + end + + return cert +end + +-- Updates a certificate +-- If the cert has a "server_names" attribute it will be used to update the server names +-- associated to the cert. +-- * If the cert had any names associated which are not on `server_names`, they will be +-- removed. +-- * Any new certificates will be added to the db. +-- Returns an error if any of the new certificates where already assigned to a cert different +-- from the one identified by cert_pk +function _Certificates:update_with_name_list(cert_pk, cert) + local db = singletons.db + local name_list, err, err_t = parse_name_list(cert.server_names, self.errors) + if err then + return nil, err, err_t + end + + if name_list then + local ok, err, err_t = + db.server_names:check_list_is_new_or_in_cert(cert_pk, name_list) + if not ok then + return nil, err, err_t + end + end + + -- update certificate if necessary + if cert.key or cert.cert then + cert.server_names = nil + cert, err, err_t = self:update(cert_pk, cert) + if err then + return nil, err, err_t + end + end + + if name_list then + cert.server_names = name_list + + local ok, err, err_t = db.server_names:update_list(cert_pk, name_list) + if not ok then + return nil, err, err_t + end + + else + cert.server_names, err, err_t = db.server_names:list_for_certificate(cert_pk) + if not cert.server_names then + return nil, err, err_t + end + end + + return cert +end + +-- Returns a single certificate provided one of its server names. Can return nil +function _Certificates:select_by_server_name(name) + local db = singletons.db + + local sn, err, err_t = db.server_names:select_by_name(name) + if err then + return nil, err, err_t + end + if not sn then + local err_t = self.errors:not_found({ name = name }) + return nil, tostring(err_t), err_t + end + + return self:select(sn.certificate) +end + +-- Returns the certificate identified by cert_pk but adds the +-- `server_names` pseudo attribute to it. It is an array of strings +-- representing the server names associated to the certificate. +function _Certificates:select_with_name_list(cert_pk) + local db = singletons.db + + local cert, err, err_t = db.certificates:select(cert_pk) + if err_t then + return nil, err, err_t + end + + if not cert then + local err_t = self.errors:not_found(cert_pk) + return nil, tostring(err_t), err_t + end + + cert.server_names, err, err_t = db.server_names:list_for_certificate(cert_pk) + if err_t then + return nil, err, err_t + end + + return cert +end + +-- Returns a page of certificates, each with the `server_names` pseudo-attribute +-- associated to them. This method does N+1 queries, but for now we are limited +-- by the DAO's select options (we can't query for "all the server names for this +-- list of certificate ids" in one go). +function _Certificates:page_with_name_list(size, offset) + local db = singletons.db + local certs, err, err_t, offset = self:page(size, offset) + if not certs then + return nil, err, err_t + end + + for i=1, #certs do + local cert = certs[i] + local server_names, err, err_t = + db.server_names:list_for_certificate({ id = cert.id }) + if not server_names then + return nil, err, err_t + end + cert.server_names = server_names + end + + return certs, nil, nil, offset +end + +-- Overrides the default delete function by cascading-deleting all the server names +-- associated to the certificate +function _Certificates:delete(cert_pk) + local db = singletons.db + + local name_list, err, err_t = + db.server_names:list_for_certificate(cert_pk) + if not name_list then + return nil, err, err_t + end + + local ok, err, err_t = db.server_names:delete_list(name_list) + if not ok then + return nil, err, err_t + end + + return self.super.delete(self, cert_pk) +end + + +return _Certificates diff --git a/kong/db/dao/init.lua b/kong/db/dao/init.lua index 2d3d4adc3c49..46da7bb01e23 100644 --- a/kong/db/dao/init.lua +++ b/kong/db/dao/init.lua @@ -139,14 +139,22 @@ local function generate_foreign_key_methods(self) self["delete_by_" .. name] = function(self, unique_value) validate_unique_value(unique_value) + local entity, err, err_t = self["select_by_" .. name](self, unique_value) + if err then + return nil, err, err_t + end + if not entity then + return 0 + end + local _, err_t = self.strategy:delete_by_field(name, unique_value) if err_t then return nil, tostring(err_t), err_t end - self:post_crud_event("delete") + self:post_crud_event("delete", entity) - return true + return entity end end end @@ -158,6 +166,7 @@ function _M.new(schema, strategy, errors) schema = schema, strategy = strategy, errors = errors, + super = DAO, -- allows custom daos to do self.super.delete(self, ...) } if schema.dao then @@ -352,14 +361,22 @@ function DAO:delete(primary_key) return nil, tostring(err_t), err_t end + local entity, err, err_t = self:select(primary_key) + if err then + return nil, err, err_t + end + if not entity then + return 0 + end + local _, err_t = self.strategy:delete(primary_key) if err_t then return nil, tostring(err_t), err_t end - self:post_crud_event("delete") + self:post_crud_event("delete", entity) - return true + return entity end diff --git a/kong/db/dao/server_names.lua b/kong/db/dao/server_names.lua new file mode 100644 index 000000000000..c5b258ef096b --- /dev/null +++ b/kong/db/dao/server_names.lua @@ -0,0 +1,159 @@ +local singletons = require "kong.singletons" + +-- A "name_list" is an array of server names like { "example.com", "foo.com" } +-- All methods containing "list" on their name act on name lists, not on lists +-- of complete ServerName entities + +-- { "a", "b", "c" } - { "a", "c" } = { "b" } +local function list_diff(list1, list2) + local set2 = {} + for i=1, #list2 do + set2[list2[i]] = true + end + + local diff = {} + local len = 0 + for i=1, #list1 do + if not set2[list1[i]] then + len = len + 1 + diff[len] = list1[i] + end + end + + return diff +end + + +local _ServerNames = {} + + +-- Truthy if all the names on the list don't exist on the db +function _ServerNames:check_list_is_new(name_list) + -- when creating a new cert (no cert_id provided): + -- dont add the certificate or any names if we have an server name conflict + -- its fairly inefficient that we have to loop twice over the datastore + -- but no support for OR queries means we gotsta! + for i=1, #name_list do + local name = name_list[i] + local row, err, err_t = singletons.db.server_names:select_by_name(name) + if err then + return nil, err, err_t + end + + if row then + -- Note: it could be that the name is not associated with any + -- certificate, but we don't handle this case. (for PostgreSQL + -- only, as C* requires a cert_id for its partition key). + local msg = "Server name already exists: " .. name + local err_t = self.errors:conflicting_input(msg) + return nil, tostring(err_t), err_t + end + end + + return 1 +end + + +-- Truthy if all the names on the list don't exist on the db or exist but are +-- associated to the given certificate +function _ServerNames:check_list_is_new_or_in_cert(cert_pk, name_list) + for i=1, #name_list do + local row, err, err_t = self:select_by_name(name_list[i]) + if err then + return nil, err, err_t + end + if row and row.certificate.id ~= cert_pk.id then + local msg = "Server Name '" .. row.name .. + "' already associated with existing " .. + "certificate (" .. row.certificate.id .. ")" + local err_t = self.errors:conflicting_input(msg) + return nil, tostring(err_t), err_t + end + end + + return 1 +end + + +-- Creates one instance of ServerName for each name in name_list +-- All created instances will be associated to the given certificate +function _ServerNames:insert_list(cert_pk, name_list) + for _, name in ipairs(name_list) do + local _, err, err_t = self:insert({ + name = name, + certificate = cert_pk, + }) + if err then + return nil, err, err_t + end + end + + return 1 +end + + +-- Deletes all server names on the given name list +function _ServerNames:delete_list(name_list) + local err_list = {} + local errors_len = 0 + local first_err_t = nil + for i = 1, #name_list do + local ok, err, err_t = self:delete_by_name(name_list[i]) + if not ok then + errors_len = errors_len + 1 + err_list[errors_len] = err + first_err_t = first_err_t or err_t + end + end + + if errors_len > 0 then + return nil, table.concat(err_list, ","), first_err_t + end + + return 1 +end + + +-- Returns the name list for a given certificate +function _ServerNames:list_for_certificate(cert_pk) + local name_list = {} + local rows, err, err_t = self:for_certificate(cert_pk) + if err then + return nil, err, err_t + end + for i = 1, #rows do + name_list[i] = rows[i].name + end + + table.sort(name_list) + return name_list +end + + +-- Replaces the names of a given certificate +-- It does not try to insert server names which are already inserted +-- It does not try to delete server names which don't exist +function _ServerNames:update_list(cert_pk, new_list) + -- Get the names currently associated to the certificate + local current_list, err, err_t = self:list_for_certificate(cert_pk) + if not current_list then + return nil, err, err_t + end + + local delete_list = list_diff(current_list, new_list) + local insert_list = list_diff(new_list, current_list) + + local ok, err, err_t = self:insert_list(cert_pk, insert_list) + if not ok then + return nil, err, err_t + end + + -- ignoring errors here + -- returning 4xx here risks invalid states and is confusing to the user + self:delete_list(delete_list) + + return 1 +end + + +return _ServerNames diff --git a/kong/db/errors.lua b/kong/db/errors.lua index e28d7824325f..e9b037c7985f 100644 --- a/kong/db/errors.lua +++ b/kong/db/errors.lua @@ -24,14 +24,16 @@ end local ERRORS = { - INVALID_PRIMARY_KEY = 1, - SCHEMA_VIOLATION = 2, - PRIMARY_KEY_VIOLATION = 3, -- primary key already exists (HTTP 400) - FOREIGN_KEY_VIOLATION = 4, -- foreign entity does not exist (HTTP 400) - UNIQUE_VIOLATION = 5, -- unique key already exists (HTTP 409) - NOT_FOUND = 6, -- WHERE clause leads nowhere (HTTP 404) - INVALID_OFFSET = 7, -- page(size, offset) is invalid - DATABASE_ERROR = 8, -- connection refused or DB error (HTTP 500) + INVALID_PRIMARY_KEY = 1, + SCHEMA_VIOLATION = 2, + PRIMARY_KEY_VIOLATION = 3, -- primary key already exists (HTTP 400) + FOREIGN_KEY_VIOLATION = 4, -- foreign entity does not exist (HTTP 400) + UNIQUE_VIOLATION = 5, -- unique key already exists (HTTP 409) + NOT_FOUND = 6, -- WHERE clause leads nowhere (HTTP 404) + INVALID_OFFSET = 7, -- page(size, offset) is invalid + DATABASE_ERROR = 8, -- connection refused or DB error (HTTP 500) + CONFLICTING_INPUT = 9, -- user-provided data generated a conflict (HTTP 409) + INVALID_INPUT = 10, -- user-provided data is not valid (HTTP 400) } @@ -47,6 +49,8 @@ local ERRORS_NAMES = { [ERRORS.NOT_FOUND] = "not found", [ERRORS.INVALID_OFFSET] = "invalid offset", [ERRORS.DATABASE_ERROR] = "database error", + [ERRORS.CONFLICTING_INPUT] = "conflicting input", + [ERRORS.INVALID_INPUT] = "invalid input", } @@ -295,6 +299,24 @@ function _M:unique_violation(unique_key) end +function _M:conflicting_input(message) + if type(message) ~= "string" then + error("message must be a string", 2) + end + + return new_err_t(self, ERRORS.CONFLICTING_INPUT, message) +end + + +function _M:invalid_input(message) + if type(message) ~= "string" then + error("message must be a string", 2) + end + + return new_err_t(self, ERRORS.INVALID_INPUT, message) +end + + function _M:invalid_offset(offset, err) if type(offset) ~= "string" then error("offset must be a string", 2) diff --git a/kong/db/init.lua b/kong/db/init.lua index 3c00849d302e..e997dc64a20d 100644 --- a/kong/db/init.lua +++ b/kong/db/init.lua @@ -3,6 +3,7 @@ local Entity = require "kong.db.schema.entity" local Errors = require "kong.db.errors" local Strategies = require "kong.db.strategies" local MetaSchema = require "kong.db.schema.metaschema" +local pl_pretty = require "pl.pretty" local fmt = string.format @@ -18,8 +19,10 @@ local setmetatable = setmetatable -- to schemas and entities since schemas will also be used -- independently from the DB module (Admin API for GUI) local CORE_ENTITIES = { - "services", "routes", + "services", + "certificates", + "server_names", } @@ -49,10 +52,10 @@ function DB.new(kong_config, strategy) local entity_schema = require("kong.db.schema.entities." .. entity_name) -- validate core entities schema via metaschema - local ok, err = MetaSchema:validate(entity_schema) + local ok, err_t = MetaSchema:validate(entity_schema) if not ok then return nil, fmt("schema of entity '%s' is invalid: %s", entity_name, - err) + pl_pretty.write(err_t)) end schemas[entity_name] = Entity.new(entity_schema) diff --git a/kong/db/schema/entities/certificates.lua b/kong/db/schema/entities/certificates.lua new file mode 100644 index 000000000000..e2f8fb7abed4 --- /dev/null +++ b/kong/db/schema/entities/certificates.lua @@ -0,0 +1,15 @@ +local typedefs = require "kong.db.schema.typedefs" + +return { + name = "certificates", + primary_key = { "id" }, + dao = "kong.db.dao.certificates", + + fields = { + { id = typedefs.uuid, }, + { created_at = { type = "integer", timestamp = true, auto = true }, }, + { cert = { type = "string", required = true}, }, + { key = { type = "string", required = true}, }, + }, + +} diff --git a/kong/db/schema/entities/server_names.lua b/kong/db/schema/entities/server_names.lua new file mode 100644 index 000000000000..c9fa7a5fdfcb --- /dev/null +++ b/kong/db/schema/entities/server_names.lua @@ -0,0 +1,15 @@ +local typedefs = require "kong.db.schema.typedefs" + +return { + name = "server_names", + primary_key = { "id" }, + dao = "kong.db.dao.server_names", + + fields = { + { id = typedefs.uuid, }, + { name = { type = "string", required = true, unique = true }, }, + { created_at = { type = "integer", timestamp = true, auto = true }, }, + { certificate = { type = "foreign", reference = "certificates", required = true }, }, + }, + +} diff --git a/spec-old-api/02-integration/04-admin_api/06-certificates_routes_spec.lua b/spec-old-api/02-integration/04-admin_api/06-certificates_routes_spec.lua deleted file mode 100644 index b301048071ab..000000000000 --- a/spec-old-api/02-integration/04-admin_api/06-certificates_routes_spec.lua +++ /dev/null @@ -1,1274 +0,0 @@ -local ssl_fixtures = require "spec-old-api.fixtures.ssl" -local dao_helpers = require "spec.02-integration.03-dao.helpers" -local DAOFactory = require "kong.dao.factory" -local helpers = require "spec.helpers" -local cjson = require "cjson" -local utils = require "kong.tools.utils" - - -local function it_content_types(title, fn) - local test_form_encoded = fn("application/x-www-form-urlencoded") - local test_json = fn("application/json") - it(title .. " with application/www-form-urlencoded", test_form_encoded) - it(title .. " with application/json", test_json) -end - - -dao_helpers.for_each_dao(function(kong_config) - -describe("Admin API: #" .. kong_config.database, function() - local client - local dao - - before_each(function() - client = assert(helpers.admin_client()) - end) - - setup(function() - dao = assert(DAOFactory.new(kong_config)) - assert(dao:run_migrations()) - - assert(helpers.start_kong({ - database = kong_config.database - })) - end) - - teardown(function() - if client then - client:close() - end - - helpers.stop_kong() - end) - - describe("/certificates", function() - before_each(function() - dao:truncate_tables() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com,bar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - assert.res_status(201, res) - end) - - describe("GET", function() - it("retrieves all certificates", function() - local res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(1, json.total) - assert.equal(1, #json.data) - assert.is_string(json.data[1].cert) - assert.is_string(json.data[1].key) - assert.same({ "foo.com", "bar.com" }, json.data[1].snis) - end) - end) - - describe("POST", function() - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foobar.com,baz.com,foobar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - assert.equals("duplicate SNI in request: foobar.com", json.message) - - -- make sure we dont add any snis - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we didnt add the certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(1, #json.data) - assert.equal(1, json.total) - end) - - it("returns a conflict when a pre-existing sni is detected", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - assert.equals("SNI already exists: foo.com", json.message) - - -- make sure we only have two snis - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - assert.equal("foo.com", json.data[1].name) - assert.equal("bar.com", json.data[2].name) - - -- make sure we only have one certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(1, json.total) - assert.equal(1, #json.data) - assert.is_string(json.data[1].cert) - assert.is_string(json.data[1].key) - assert.same({ "foo.com", "bar.com" }, json.data[1].snis) - end) - - it_content_types("creates a certificate", function(content_type) - return function() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foobar.com,baz.com", - }, - headers = { ["Content-Type"] = content_type }, - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - assert.is_string(json.cert) - assert.is_string(json.key) - assert.same({ "foobar.com", "baz.com" }, json.snis) - end - end) - - it_content_types("returns snis as [] when none is set", function(content_type) - return function() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }, - headers = { ["Content-Type"] = content_type }, - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - assert.is_string(json.cert) - assert.is_string(json.key) - assert.matches('"snis":[]', body, nil, true) - end - end) - end) - - describe("PUT", function() - local cert_foo - local cert_bar - - before_each(function() - dao:truncate_tables() - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_foo = cjson.decode(body) - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "bar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_bar = cjson.decode(body) - end) - - it("creates a certificate if ID is not present in the body", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - - assert.is_string(json.cert) - assert.is_string(json.key) - assert.same({ "baz.com" }, json.snis) - - -- make sure we added an sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(3, #json.data) - assert.equal(3, json.total) - - -- make sure we added our certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(3, #json.data) - assert.equal(3, json.total) - end) - - it("returns 404 for a random non-existing id", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = utils.uuid(), - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - assert.res_status(404, res) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only certificate is specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - cert = "cert_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("key is required", json.key) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only key is specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - key = "key_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("cert is required", json.cert) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates snis associated with a certificate", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.same({ "baz.com" }, json.snis) - - -- make sure number of snis don't change - -- since we delete foo.com and added baz.com - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - local sni_names = {} - table.insert(sni_names, json.data[1].name) - table.insert(sni_names, json.data[2].name) - assert.are.same({ "baz.com", "bar.com" }, sni_names) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates only the certificate if no snis are specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - cert = "bar_cert", - key = "bar_key", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - -- make sure certificate got updated and sni remains the same - assert.same({ "bar.com" }, json.snis) - assert.same("bar_cert", json.cert) - assert.same("bar_key", json.key) - - -- make sure the certificate got updated in DB - res = assert(client:send { - method = "GET", - path = "/certificates/" .. cert_bar.id, - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal("bar_cert", json.cert) - assert.equal("bar_key", json.key) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - snis = "baz.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("duplicate SNI in request: baz.com", json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when a pre-existing sni present in " .. - "the request is associated with another certificate", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - snis = "foo.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("SNI 'foo.com' already associated with " .. - "existing certificate (" .. cert_foo.id .. ")", - json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("deletes all snis from a certificate if snis field is JSON null", function() - -- Note: we currently do not support unsetting a field with - -- form-urlencoded requests. This depends on upcoming work - -- to the Admin API. We here follow the road taken by: - -- https://github.com/Kong/kong/pull/2700 - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - snis = ngx.null, - id = cert_bar.id, - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.equal(0, #json.snis) - assert.matches('"snis":[]', body, nil, true) - - -- make sure the sni was deleted - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(1, #json.data) - assert.equal(1, json.total) - assert.equal("foo.com", json.data[1].name) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - end) - end) - - describe("/certificates/:sni_or_uuid", function() - before_each(function() - dao:truncate_tables() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com,bar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - assert.res_status(201, res) - end) - - describe("GET", function() - it("retrieves a certificate by SNI", function() - local res1 = assert(client:send { - method = "GET", - path = "/certificates/foo.com", - }) - - local body1 = assert.res_status(200, res1) - local json1 = cjson.decode(body1) - - local res2 = assert(client:send { - method = "GET", - path = "/certificates/bar.com", - }) - - local body2 = assert.res_status(200, res2) - local json2 = cjson.decode(body2) - - assert.is_string(json1.cert) - assert.is_string(json1.key) - assert.same({ "foo.com", "bar.com" }, json1.snis) - assert.same(json1, json2) - end) - end) - - describe("PATCH", function() - local cert_foo - local cert_bar - - before_each(function() - dao:truncate_tables() - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_foo = cjson.decode(body) - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "bar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_bar = cjson.decode(body) - end) - - it_content_types("updates a certificate by SNI", function(content_type) - return function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/foo.com", - body = { - cert = "foo_cert", - key = "foo_key", - }, - headers = { ["Content-Type"] = content_type } - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.equal("foo_cert", json.cert) - end - end) - - it("returns 404 for a random non-existing id", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. utils.uuid(), - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - assert.res_status(404, res) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only certificate is specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - cert = "cert_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("key is required", json.key) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only key is specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - key = "key_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("cert is required", json.cert) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates snis associated with a certificate", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.same({ "baz.com" }, json.snis) - - -- make sure number of snis don't change - -- since we delete foo.com and added baz.com - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - local sni_names = {} - table.insert(sni_names, json.data[1].name) - table.insert(sni_names, json.data[2].name) - assert.are.same( { "baz.com", "bar.com" } , sni_names) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates only the certificate if no snis are specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, - body = { - cert = "bar_cert", - key = "bar_key", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - -- make sure certificate got updated and sni remains the same - assert.same({ "bar.com" }, json.snis) - assert.same("bar_cert", json.cert) - assert.same("bar_key", json.key) - - -- make sure the certificate got updated in DB - res = assert(client:send { - method = "GET", - path = "/certificates/" .. cert_bar.id, - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal("bar_cert", json.cert) - assert.equal("bar_key", json.key) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, - body = { - snis = "baz.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("duplicate SNI in request: baz.com", json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when a pre-existing sni present in " .. - "the request is associated with another certificate", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, - body = { - snis = "foo.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("SNI 'foo.com' already associated with " .. - "existing certificate (" .. cert_foo.id .. ")", - json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("deletes all snis from a certificate if snis field is JSON null", function() - -- Note: we currently do not support unsetting a field with - -- form-urlencoded requests. This depends on upcoming work - -- to the Admin API. We here follow the road taken by: - -- https://github.com/Kong/kong/pull/2700 - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, - body = { - snis = ngx.null, - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.equal(0, #json.snis) - assert.matches('"snis":[]', body, nil, true) - - -- make sure the sni was deleted - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(1, #json.data) - assert.equal(1, json.total) - assert.equal("foo.com", json.data[1].name) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - end) - - describe("DELETE", function() - it("deletes a certificate and all related SNIs", function() - local res = assert(client:send { - method = "DELETE", - path = "/certificates/foo.com", - }) - - assert.res_status(204, res) - - res = assert(client:send { - method = "GET", - path = "/certificates/foo.com", - }) - - assert.res_status(404, res) - - res = assert(client:send { - method = "GET", - path = "/certificates/bar.com", - }) - - assert.res_status(404, res) - end) - - it("deletes a certificate by id", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = "foo", - key = "bar", - }, - headers = { ["Content-Type"] = "application/json" } - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - - res = assert(client:send { - method = "DELETE", - path = "/certificates/" .. json.id, - }) - - assert.res_status(204, res) - end) - end) - end) - - - describe("/snis", function() - local ssl_certificate - - before_each(function() - dao:truncate_tables() - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) - end) - - describe("POST", function() - before_each(function() - dao:truncate_tables() - - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) - end) - - describe("errors", function() - it("certificate doesn't exist", function() - local res = assert(client:send { - method = "POST", - path = "/snis", - body = { - name = "bar.com", - ssl_certificate_id = "585e4c16-c656-11e6-8db9-5f512d8a12cd", - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(404, res) - local json = cjson.decode(body) - assert.same({ ssl_certificate_id = "does not exist with value " - .. "'585e4c16-c656-11e6-8db9-5f512d8a12cd'" }, json) - end) - end) - - it_content_types("creates a SNI", function(content_type) - return function() - local res = assert(client:send { - method = "POST", - path = "/snis", - body = { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }, - headers = { ["Content-Type"] = content_type }, - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - assert.equal("foo.com", json.name) - assert.equal(ssl_certificate.id, json.ssl_certificate_id) - end - end) - - it("returns a conflict when an SNI already exists", function() - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) - - local res = assert(client:send { - method = "POST", - path = "/snis", - body = { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - assert.equals("already exists with value 'foo.com'", json.name) - end) - end) - - describe("GET", function() - it("retrieves a SNI", function() - local res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(1, #json.data) - assert.equal(1, json.total) - assert.equal("foo.com", json.data[1].name) - assert.equal(ssl_certificate.id, json.data[1].ssl_certificate_id) - end) - end) - end) - - describe("/snis/:name", function() - local ssl_certificate - - before_each(function() - dao:truncate_tables() - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) - end) - - describe("GET", function() - it("retrieves a SNI", function() - local res = assert(client:send { - mathod = "GET", - path = "/snis/foo.com", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal("foo.com", json.name) - assert.equal(ssl_certificate.id, json.ssl_certificate_id) - end) - end) - - describe("PATCH", function() - do - local test = it - if kong_config.database == "cassandra" then - test = pending - end - - test("updates a SNI", function() - -- SKIP: this test fails with Cassandra because the PRIMARY KEY - -- used by the C* table is a composite of (name, - -- ssl_certificate_id), and hence, we cannot update the - -- ssl_certificate_id field because it is in the `SET` part of the - -- query built by the DAO, but in C*, one cannot change a value - -- from the clustering key. - local ssl_certificate_2 = assert(dao.ssl_certificates:insert { - cert = "foo", - key = "bar", - }) - - local res = assert(client:send { - method = "PATCH", - path = "/snis/foo.com", - body = { - ssl_certificate_id = ssl_certificate_2.id, - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(ssl_certificate_2.id, json.ssl_certificate_id) - end) - end - end) - - describe("DELETE", function() - it("deletes a SNI", function() - local res = assert(client:send { - method = "DELETE", - path = "/snis/foo.com", - }) - - assert.res_status(204, res) - end) - end) - end) -end) - -end) diff --git a/spec-old-api/02-integration/05-proxy/05-ssl_spec.lua b/spec-old-api/02-integration/05-proxy/05-ssl_spec.lua index b648323461a0..417f1d82928b 100644 --- a/spec-old-api/02-integration/05-proxy/05-ssl_spec.lua +++ b/spec-old-api/02-integration/05-proxy/05-ssl_spec.lua @@ -79,7 +79,7 @@ describe("SSL", function() body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "example.com,ssl1.com", + server_names = "example.com,ssl1.com", }, headers = { ["Content-Type"] = "application/json" }, }) diff --git a/spec-old-api/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua b/spec-old-api/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua index 4e74d1164ae2..d665b6b610f7 100644 --- a/spec-old-api/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua +++ b/spec-old-api/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua @@ -215,7 +215,7 @@ describe("core entities are invalidated with db: #" .. strategy, function() -- ssl_certificates ------------------- - describe("#o ssl_certificates / SNIs", function() + describe("#o ssl_certificates / server_names", function() local function get_cert(port, sni) local pl_utils = require "pl.utils" @@ -244,14 +244,14 @@ describe("core entities are invalidated with db: #" .. strategy, function() assert.matches("CN=localhost", cert_2, nil, true) end) - it("on certificate+SNI create", function() + it("on certificate+Server Name create", function() local admin_res = assert(admin_client_1:send { method = "POST", path = "/certificates", body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "ssl-example.com", + server_names = "ssl-example.com", }, headers = { ["Content-Type"] = "application/json", @@ -273,7 +273,7 @@ describe("core entities are invalidated with db: #" .. strategy, function() it("on certificate delete+re-creation", function() -- TODO: PATCH/PUT update are currently not possible - -- with the admin API because snis have their name as their + -- with the admin API because server_names have their name as their -- primary key and the DAO has limited support for such updates. local admin_res = assert(admin_client_1:send { @@ -288,7 +288,7 @@ describe("core entities are invalidated with db: #" .. strategy, function() body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "new-ssl-example.com", + server_names = "new-ssl-example.com", }, headers = { ["Content-Type"] = "application/json", @@ -316,7 +316,7 @@ describe("core entities are invalidated with db: #" .. strategy, function() it("on certificate update", function() -- update our certificate *without* updating the - -- attached SNI + -- attached Server Name local admin_res = assert(admin_client_1:send { method = "PATCH", @@ -343,19 +343,19 @@ describe("core entities are invalidated with db: #" .. strategy, function() assert.matches("CN=ssl-alt.com", cert_2, nil, true) end) - pending("on SNI update", function() - -- Pending: currently, SNIs cannot be updated: + pending("on Server Name update", function() + -- Pending: currently, server_names cannot be updated: -- - A PATCH updating the name property would not work, since -- the URI path expects the current name, and so does the -- query fetchign the row to be updated -- -- -- - -- update our SNI but leave certificate untouched + -- update our Server Name but leave certificate untouched local admin_res = assert(admin_client_1:send { method = "PATCH", - path = "/snis/new-ssl-example.com", + path = "/server_names/new-ssl-example.com", body = { name = "updated-sni.com", }, diff --git a/spec/02-integration/000-new-dao/02-db_core_entities_spec.lua b/spec/02-integration/000-new-dao/02-db_core_entities_spec.lua index 387fe7e20ba1..65bc7181746c 100644 --- a/spec/02-integration/000-new-dao/02-db_core_entities_spec.lua +++ b/spec/02-integration/000-new-dao/02-db_core_entities_spec.lua @@ -432,7 +432,7 @@ for _, strategy in helpers.each_strategy() do local ok, err, err_t = db.routes:delete({ id = u }) - assert.is_true(ok) + assert.is_truthy(ok) assert.is_nil(err_t) assert.is_nil(err) end) @@ -447,7 +447,7 @@ for _, strategy in helpers.each_strategy() do }) assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) local route_in_db, err, err_t = db.routes:select({ id = route.id @@ -1177,7 +1177,7 @@ for _, strategy in helpers.each_strategy() do local ok, err, err_t = db.services:delete({ id = u }) - assert.is_true(ok) + assert.is_truthy(ok) assert.is_nil(err_t) assert.is_nil(err) end) @@ -1192,7 +1192,7 @@ for _, strategy in helpers.each_strategy() do }) assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) local service_in_db, err, err_t = db.services:select({ id = service.id @@ -1225,7 +1225,7 @@ for _, strategy in helpers.each_strategy() do -- I/O it("returns nothing if the Service does not exist", function() local ok, err, err_t = db.services:delete_by_name("service_10") - assert.is_true(ok) + assert.is_truthy(ok) assert.is_nil(err_t) assert.is_nil(err) end) @@ -1234,7 +1234,7 @@ for _, strategy in helpers.each_strategy() do local ok, err, err_t = db.services:delete_by_name("service_1") assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) local service_in_db, err, err_t = db.services:select({ id = service.id @@ -1365,7 +1365,7 @@ for _, strategy in helpers.each_strategy() do local ok, err, err_t = db.routes:delete({ id = route.id }) assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) local service_in_db, err, err_t = db.services:select({ id = service.id diff --git a/spec/02-integration/01-helpers/01-blueprints_spec.lua b/spec/02-integration/01-helpers/01-blueprints_spec.lua index dd01b0afaec1..4eefa3e4d02a 100644 --- a/spec/02-integration/01-helpers/01-blueprints_spec.lua +++ b/spec/02-integration/01-helpers/01-blueprints_spec.lua @@ -107,16 +107,20 @@ dao_helpers.for_each_dao(function(kong_config) assert.same({ "email", "profile" }, p.config.scopes) end) - it("inserts ssl certificates", function() - local c = bp.ssl_certificates:insert() + it("inserts certificates", function() + local c = bp.certificates:insert() assert.equal("string", type(c.cert)) assert.equal("string", type(c.key)) end) - it("inserts ssl server names", function() - local c = bp.ssl_certificates:insert() - local s = bp.ssl_servers_names:insert({ ssl_certificate_id = c.id }) + it("inserts server names", function() + local c = bp.certificates:insert() + local s = bp.server_names:insert({ certificate = c }) assert.equal("string", type(s.name)) + + local s2 = bp.server_names:insert() + assert.equal("string", type(s2.name)) + assert.equal("string", type(s2.certificate.id)) end) it("inserts consumers", function() diff --git a/spec/02-integration/03-dao/04-constraints_spec.lua b/spec/02-integration/03-dao/04-constraints_spec.lua index 511bfa5f9543..3d51f63af6ae 100644 --- a/spec/02-integration/03-dao/04-constraints_spec.lua +++ b/spec/02-integration/03-dao/04-constraints_spec.lua @@ -315,7 +315,7 @@ dao_helpers.for_each_dao(function(kong_config) } assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) -- no more Service local api, err = db.services:select { @@ -364,7 +364,7 @@ dao_helpers.for_each_dao(function(kong_config) } assert.is_nil(err_t) assert.is_nil(err) - assert.is_true(ok) + assert.is_truthy(ok) -- no more Route local api, err = db.routes:select { diff --git a/spec/02-integration/04-admin_api/06-certificates_routes_spec.lua b/spec/02-integration/04-admin_api/06-certificates_routes_spec.lua index a3cdca937621..cabb583d12f0 100644 --- a/spec/02-integration/04-admin_api/06-certificates_routes_spec.lua +++ b/spec/02-integration/04-admin_api/06-certificates_routes_spec.lua @@ -1,6 +1,4 @@ local ssl_fixtures = require "spec.fixtures.ssl" -local dao_helpers = require "spec.02-integration.03-dao.helpers" -local DAOFactory = require "kong.dao.factory" local helpers = require "spec.helpers" local cjson = require "cjson" local utils = require "kong.tools.utils" @@ -14,11 +12,12 @@ local function it_content_types(title, fn) end -dao_helpers.for_each_dao(function(kong_config) +for _, strategy in helpers.each_strategy() do -describe("Admin API: #" .. kong_config.database, function() +describe("Admin API: #" .. strategy, function() local client - local dao + + local bp, db, dao before_each(function() client = assert(helpers.admin_client()) @@ -31,11 +30,11 @@ describe("Admin API: #" .. kong_config.database, function() end) setup(function() - dao = assert(DAOFactory.new(kong_config)) + bp, db, dao = helpers.get_db_utils(strategy) assert(dao:run_migrations()) assert(helpers.start_kong({ - database = kong_config.database + database = strategy, })) end) @@ -45,131 +44,94 @@ describe("Admin API: #" .. kong_config.database, function() describe("/certificates", function() before_each(function() - dao:truncate_tables() - local res = assert(client:send { - method = "POST", - path = "/certificates", + assert(db:truncate()) + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foo.com,bar.com", + server_names = "foo.com,bar.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) - assert.res_status(201, res) end) describe("GET", function() it("retrieves all certificates", function() - local res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + local res = client:get("/certificates") local body = assert.res_status(200, res) local json = cjson.decode(body) - assert.equal(1, json.total) assert.equal(1, #json.data) assert.is_string(json.data[1].cert) assert.is_string(json.data[1].key) - assert.same({ "foo.com", "bar.com" }, json.data[1].snis) + assert.same({ "bar.com", "foo.com" }, json.data[1].server_names) end) end) describe("POST", function() - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", + it("returns a conflict when duplicated server_names are present in the request", function() + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foobar.com,baz.com,foobar.com", + server_names = "foobar.com,baz.com,foobar.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) - - local body = assert.res_status(409, res) + local body = assert.res_status(400, res) local json = cjson.decode(body) - assert.equals("duplicate SNI in request: foobar.com", json.message) - - -- make sure we dont add any snis - res = assert(client:send { - method = "GET", - path = "/snis", - }) + assert.equals("duplicate server name in request: foobar.com", json.message) + -- make sure we dont add any server_names + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) -- make sure we didnt add the certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(1, #json.data) - assert.equal(1, json.total) end) it("returns a conflict when a pre-existing sni is detected", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foo.com", + server_names = "foo.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) - local body = assert.res_status(409, res) local json = cjson.decode(body) - assert.equals("SNI already exists: foo.com", json.message) - - -- make sure we only have two snis - res = assert(client:send { - method = "GET", - path = "/snis", - }) + assert.equals("Server name already exists: foo.com", json.message) + -- make sure we only have two server_names + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) - assert.equal("foo.com", json.data[1].name) - assert.equal("bar.com", json.data[2].name) + local names = { json.data[1].name, json.data[2].name } + table.sort(names) + assert.same({ "bar.com", "foo.com" }, names) -- make sure we only have one certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(1, json.total) assert.equal(1, #json.data) - assert.is_string(json.data[1].cert) - assert.is_string(json.data[1].key) - assert.same({ "foo.com", "bar.com" }, json.data[1].snis) + assert.same({ "bar.com", "foo.com" }, json.data[1].server_names) end) - it_content_types("creates a certificate", function(content_type) + it_content_types("creates a certificate and returns it with the server_names pseudo-property", function(content_type) return function() - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foobar.com,baz.com", + server_names = "foobar.com,baz.com", }, headers = { ["Content-Type"] = content_type }, }) @@ -178,15 +140,13 @@ describe("Admin API: #" .. kong_config.database, function() local json = cjson.decode(body) assert.is_string(json.cert) assert.is_string(json.key) - assert.same({ "foobar.com", "baz.com" }, json.snis) + assert.same({ "baz.com", "foobar.com" }, json.server_names) end end) - it_content_types("returns snis as [] when none is set", function(content_type) + it_content_types("returns server_names as [] when none is set", function(content_type) return function() - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, @@ -198,437 +158,20 @@ describe("Admin API: #" .. kong_config.database, function() local json = cjson.decode(body) assert.is_string(json.cert) assert.is_string(json.key) - assert.matches('"snis":[]', body, nil, true) + assert.matches('"server_names":[]', body, nil, true) end end) end) - - describe("PUT", function() - local cert_foo - local cert_bar - - before_each(function() - dao:truncate_tables() - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "foo.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_foo = cjson.decode(body) - - local res = assert(client:send { - method = "POST", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "bar.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - local body = assert.res_status(201, res) - cert_bar = cjson.decode(body) - end) - - it("creates a certificate if ID is not present in the body", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(201, res) - local json = cjson.decode(body) - - assert.is_string(json.cert) - assert.is_string(json.key) - assert.same({ "baz.com" }, json.snis) - - -- make sure we added an sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(3, #json.data) - assert.equal(3, json.total) - - -- make sure we added our certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(3, #json.data) - assert.equal(3, json.total) - end) - - it("returns 404 for a random non-existing id", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = utils.uuid(), - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - assert.res_status(404, res) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only certificate is specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - cert = "cert_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("key is required", json.key) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only key is specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - key = "key_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("cert is required", json.cert) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates snis associated with a certificate", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_foo.id, - snis = "baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.same({ "baz.com" }, json.snis) - - -- make sure number of snis don't change - -- since we delete foo.com and added baz.com - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - local sni_names = {} - table.insert(sni_names, json.data[1].name) - table.insert(sni_names, json.data[2].name) - assert.are.same({ "baz.com", "bar.com" }, sni_names) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("updates only the certificate if no snis are specified", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - cert = "bar_cert", - key = "bar_key", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - -- make sure certificate got updated and sni remains the same - assert.same({ "bar.com" }, json.snis) - assert.same("bar_cert", json.cert) - assert.same("bar_key", json.key) - - -- make sure the certificate got updated in DB - res = assert(client:send { - method = "GET", - path = "/certificates/" .. cert_bar.id, - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal("bar_cert", json.cert) - assert.equal("bar_key", json.key) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - snis = "baz.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("duplicate SNI in request: baz.com", json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns a conflict when a pre-existing sni present in " .. - "the request is associated with another certificate", function() - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - id = cert_bar.id, - snis = "foo.com,baz.com", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(409, res) - local json = cjson.decode(body) - - assert.equals("SNI 'foo.com' already associated with " .. - "existing certificate (" .. cert_foo.id .. ")", - json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("deletes all snis from a certificate if snis field is JSON null", function() - -- Note: we currently do not support unsetting a field with - -- form-urlencoded requests. This depends on upcoming work - -- to the Admin API. We here follow the road taken by: - -- https://github.com/Kong/kong/pull/2700 - local res = assert(client:send { - method = "PUT", - path = "/certificates", - body = { - snis = ngx.null, - id = cert_bar.id, - }, - headers = { ["Content-Type"] = "application/json" }, - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - - assert.equal(0, #json.snis) - assert.matches('"snis":[]', body, nil, true) - - -- make sure the sni was deleted - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(1, #json.data) - assert.equal(1, json.total) - assert.equal("foo.com", json.data[1].name) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - end) end) - describe("/certificates/:sni_or_uuid", function() + describe("/certificates/cert_id_or_server_name", function() before_each(function() - dao:truncate_tables() - local res = assert(client:send { - method = "POST", - path = "/certificates", + assert(db:truncate()) + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foo.com,bar.com", + server_names = "foo.com,bar.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) @@ -637,42 +180,28 @@ describe("Admin API: #" .. kong_config.database, function() end) describe("GET", function() - it("retrieves a certificate by SNI", function() - local res1 = assert(client:send { - method = "GET", - path = "/certificates/foo.com", - }) - + it("retrieves a certificate by Server Name", function() + local res1 = client:get("/certificates/foo.com") local body1 = assert.res_status(200, res1) local json1 = cjson.decode(body1) - local res2 = assert(client:send { - method = "GET", - path = "/certificates/bar.com", - }) - + local res2 = client:get("/certificates/bar.com") local body2 = assert.res_status(200, res2) local json2 = cjson.decode(body2) assert.is_string(json1.cert) assert.is_string(json1.key) - assert.same({ "foo.com", "bar.com" }, json1.snis) + assert.same({ "bar.com", "foo.com" }, json1.server_names) assert.same(json1, json2) end) it("returns 404 for a random non-existing uuid", function() - local res = assert(client:send { - method = "GET", - path = "/certificates/" .. utils.uuid(), - }) + local res = client:get("/certificates/" .. utils.uuid()) assert.res_status(404, res) end) - it("returns 404 for a random non-existing SNI", function() - local res = assert(client:send { - method = "GET", - path = "/certificates/doesntexist.com", - }) + it("returns 404 for a random non-existing Server Name", function() + local res = client:get("/certificates/doesntexist.com") assert.res_status(404, res) end) end) @@ -682,28 +211,24 @@ describe("Admin API: #" .. kong_config.database, function() local cert_bar before_each(function() - dao:truncate_tables() + assert(db:truncate()) - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "foo.com", + server_names = "foo.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) local body = assert.res_status(201, res) cert_foo = cjson.decode(body) - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "bar.com", + server_names = "bar.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) @@ -711,11 +236,9 @@ describe("Admin API: #" .. kong_config.database, function() cert_bar = cjson.decode(body) end) - it_content_types("updates a certificate by SNI", function(content_type) + it_content_types("updates a certificate by Server Name", function(content_type) return function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/foo.com", + local res = client:patch("/certificates/foo.com", { body = { cert = "foo_cert", key = "foo_key", @@ -731,13 +254,11 @@ describe("Admin API: #" .. kong_config.database, function() end) it("returns 404 for a random non-existing id", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. utils.uuid(), + local res = client:patch("/certificates/" .. utils.uuid(), { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "baz.com", + server_names = "baz.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) @@ -745,149 +266,48 @@ describe("Admin API: #" .. kong_config.database, function() assert.res_status(404, res) -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only certificate is specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - cert = "cert_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("key is required", json.key) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - - local body = assert.res_status(200, res) - local json = cjson.decode(body) - assert.equal(2, #json.data) - assert.equal(2, json.total) - - -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - - body = assert.res_status(200, res) - json = cjson.decode(body) - assert.equal(2, json.total) - assert.equal(2, #json.data) - end) - - it("returns Bad Request if only key is specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - key = "key_foo", - }, - headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, - }) - - local body = assert.res_status(400, res) - local json = cjson.decode(body) - assert.equals("cert is required", json.cert) - - -- make sure we did not add any sni - res = assert(client:send { - method = "GET", - path = "/snis", - }) - + res = client:get("/server_names") local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) - it("updates snis associated with a certificate", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_foo.id, - body = { - snis = "baz.com", - }, + it("updates server_names associated with a certificate", function() + local res = client:patch("/certificates/" .. cert_foo.id, { + body = { server_names = "baz.com" }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) - assert.same({ "baz.com" }, json.snis) + assert.same({ "baz.com" }, json.server_names) - -- make sure number of snis don't change + -- make sure number of server_names don't change -- since we delete foo.com and added baz.com - res = assert(client:send { - method = "GET", - path = "/snis", - }) - + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) - local sni_names = {} - table.insert(sni_names, json.data[1].name) - table.insert(sni_names, json.data[2].name) - assert.are.same( { "baz.com", "bar.com" } , sni_names) + local names = { json.data[1].name, json.data[2].name } + table.sort(names) + assert.are.same( { "bar.com", "baz.com" } , names) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) - it("updates only the certificate if no snis are specified", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, + it("updates only the certificate if no server_names are specified", function() + local res = client:patch( "/certificates/" .. cert_bar.id, { body = { cert = "bar_cert", key = "bar_key", @@ -899,89 +319,60 @@ describe("Admin API: #" .. kong_config.database, function() local json = cjson.decode(body) -- make sure certificate got updated and sni remains the same - assert.same({ "bar.com" }, json.snis) + assert.same({ "bar.com" }, json.server_names) assert.same("bar_cert", json.cert) assert.same("bar_key", json.key) -- make sure the certificate got updated in DB - res = assert(client:send { - method = "GET", - path = "/certificates/" .. cert_bar.id, - }) - + res = client:get("/certificates/" .. cert_bar.id) body = assert.res_status(200, res) json = cjson.decode(body) assert.equal("bar_cert", json.cert) assert.equal("bar_key", json.key) - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - + -- make sure number of server_names don't change + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) - it("returns a conflict when duplicates snis are present in the request", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, + it("returns a conflict when duplicates server_names are present in the request", function() + local res = client:patch("/certificates/" .. cert_bar.id, { body = { - snis = "baz.com,baz.com", + server_names = "baz.com,baz.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) - - local body = assert.res_status(409, res) + local body = assert.res_status(400, res) local json = cjson.decode(body) - assert.equals("duplicate SNI in request: baz.com", json.message) - - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) + assert.equals("duplicate server name in request: baz.com", json.message) + -- make sure number of server_names don't change + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) - it("returns a conflict when a pre-existing sni present in " .. + it("returns a conflict when a pre-existing server name present in " .. "the request is associated with another certificate", function() - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, + local res = client:patch("/certificates/" .. cert_bar.id, { body = { - snis = "foo.com,baz.com", + server_names = "foo.com,baz.com", }, headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }, }) @@ -989,120 +380,82 @@ describe("Admin API: #" .. kong_config.database, function() local body = assert.res_status(409, res) local json = cjson.decode(body) - assert.equals("SNI 'foo.com' already associated with " .. + assert.equals("Server Name 'foo.com' already associated with " .. "existing certificate (" .. cert_foo.id .. ")", json.message) - -- make sure number of snis don't change - res = assert(client:send { - method = "GET", - path = "/snis", - }) - + -- make sure number of server_names don't change + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(2, #json.data) - assert.equal(2, json.total) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) - it("deletes all snis from a certificate if snis field is JSON null", function() + it("deletes all server_names from a certificate if server_names field is JSON null", function() -- Note: we currently do not support unsetting a field with -- form-urlencoded requests. This depends on upcoming work -- to the Admin API. We here follow the road taken by: -- https://github.com/Kong/kong/pull/2700 - local res = assert(client:send { - method = "PATCH", - path = "/certificates/" .. cert_bar.id, + local res = client:patch("/certificates/" .. cert_bar.id, { body = { - snis = ngx.null, + server_names = ngx.null, }, headers = { ["Content-Type"] = "application/json" }, }) - local body = assert.res_status(200, res) local json = cjson.decode(body) - assert.equal(0, #json.snis) - assert.matches('"snis":[]', body, nil, true) + assert.equal(0, #json.server_names) + assert.matches('"server_names":[]', body, nil, true) -- make sure the sni was deleted - res = assert(client:send { - method = "GET", - path = "/snis", - }) - + res = client:get("/server_names") body = assert.res_status(200, res) json = cjson.decode(body) assert.equal(1, #json.data) - assert.equal(1, json.total) assert.equal("foo.com", json.data[1].name) -- make sure we did not add any certificate - res = assert(client:send { - method = "GET", - path = "/certificates", - }) - + res = client:get("/certificates") body = assert.res_status(200, res) json = cjson.decode(body) - assert.equal(2, json.total) assert.equal(2, #json.data) end) end) describe("DELETE", function() - it("deletes a certificate and all related SNIs", function() - local res = assert(client:send { - method = "DELETE", - path = "/certificates/foo.com", - }) + it("deletes a certificate and all related server_names", function() + local res = client:delete("/certificates/foo.com") assert.res_status(204, res) - res = assert(client:send { - method = "GET", - path = "/certificates/foo.com", - }) + res = client:get("/server_names/foo.com") assert.res_status(404, res) - res = assert(client:send { - method = "GET", - path = "/certificates/bar.com", - }) + res = client:get("/server_names/bar.com") assert.res_status(404, res) end) it("deletes a certificate by id", function() - local res = assert(client:send { - method = "POST", - path = "/certificates", + local res = client:post("/certificates", { body = { cert = "foo", key = "bar", }, headers = { ["Content-Type"] = "application/json" } }) - local body = assert.res_status(201, res) local json = cjson.decode(body) - res = assert(client:send { - method = "DELETE", - path = "/certificates/" .. json.id, - }) + local res = client:delete("/certificates/" .. json.id) assert.res_status(204, res) end) @@ -1110,58 +463,40 @@ describe("Admin API: #" .. kong_config.database, function() end) - describe("/snis", function() - local ssl_certificate - - before_each(function() - dao:truncate_tables() - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) - end) - + describe("/server_names", function() describe("POST", function() + + local certificate before_each(function() - dao:truncate_tables() + assert(db:truncate()) - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) + certificate = bp.certificates:insert() end) describe("errors", function() it("certificate doesn't exist", function() - local res = assert(client:send { - method = "POST", - path = "/snis", + local res = client:post("/server_names", { body = { - name = "bar.com", - ssl_certificate_id = "585e4c16-c656-11e6-8db9-5f512d8a12cd", + name = "bar.com", + certificate = { id = "585e4c16-c656-11e6-8db9-5f512d8a12cd" }, }, headers = { ["Content-Type"] = "application/json" }, }) - local body = assert.res_status(404, res) + local body = assert.res_status(400, res) local json = cjson.decode(body) - assert.same({ ssl_certificate_id = "does not exist with value " - .. "'585e4c16-c656-11e6-8db9-5f512d8a12cd'" }, json) + assert.same("the foreign key '{id=\"585e4c16-c656-11e6-8db9-5f512d8a12cd\"}' " .. + "does not reference an existing 'certificates' entity.", + json.message) end) end) - it_content_types("creates a SNI", function(content_type) + it_content_types("creates a Server Name", function(content_type) return function() - local res = assert(client:send { - method = "POST", - path = "/snis", + local res = client:post("/server_names", { body = { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, + name = "foo.com", + certificate = { id = certificate.id }, }, headers = { ["Content-Type"] = content_type }, }) @@ -1169,124 +504,111 @@ describe("Admin API: #" .. kong_config.database, function() local body = assert.res_status(201, res) local json = cjson.decode(body) assert.equal("foo.com", json.name) - assert.equal(ssl_certificate.id, json.ssl_certificate_id) + assert.equal(certificate.id, json.certificate.id) end end) - it("returns a conflict when an SNI already exists", function() - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) + it("returns a conflict when an Server Name already exists", function() + bp.server_names:insert { + name = "foo.com", + certificate = certificate, + } - local res = assert(client:send { - method = "POST", - path = "/snis", - body = { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }, - headers = { ["Content-Type"] = "application/json" }, - }) + local res = client:post("/server_names", { + body = { + name = "foo.com", + certificate = { id = certificate.id }, + }, + headers = { ["Content-Type"] = "application/json" }, + }) - local body = assert.res_status(409, res) - local json = cjson.decode(body) - assert.equals("already exists with value 'foo.com'", json.name) + local body = assert.res_status(409, res) + local json = cjson.decode(body) + assert.equals("unique constraint violation", json.name) end) end) describe("GET", function() - it("retrieves a SNI", function() - local res = assert(client:send { - method = "GET", - path = "/snis", - }) - + it("retrieves a list of server names", function() + assert(db:truncate()) + local certificate = bp.certificates:insert() + bp.server_names:insert { + name = "foo.com", + certificate = certificate, + } + + local res = client:get("/server_names") local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal(1, #json.data) - assert.equal(1, json.total) assert.equal("foo.com", json.data[1].name) - assert.equal(ssl_certificate.id, json.data[1].ssl_certificate_id) + assert.equal(certificate.id, json.data[1].certificate.id) end) end) end) - describe("/snis/:name", function() - local ssl_certificate + describe("/server_names/:name", function() + local certificate before_each(function() - dao:truncate_tables() - ssl_certificate = assert(dao.ssl_certificates:insert { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - }) - assert(dao.ssl_servers_names:insert { - name = "foo.com", - ssl_certificate_id = ssl_certificate.id, - }) + assert(db:truncate()) + certificate = bp.certificates:insert() + bp.server_names:insert { + name = "foo.com", + certificate = certificate, + } end) describe("GET", function() - it("retrieves a SNI", function() - local res = assert(client:send { - mathod = "GET", - path = "/snis/foo.com", - }) - + it("retrieves a Server Name", function() + local res = client:get("/server_names/foo.com") local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("foo.com", json.name) - assert.equal(ssl_certificate.id, json.ssl_certificate_id) + assert.equal(certificate.id, json.certificate.id) end) end) describe("PATCH", function() do local test = it - if kong_config.database == "cassandra" then + if strategy == "cassandra" then test = pending end - test("updates a SNI", function() + test("updates a Server Name", function() -- SKIP: this test fails with Cassandra because the PRIMARY KEY -- used by the C* table is a composite of (name, - -- ssl_certificate_id), and hence, we cannot update the - -- ssl_certificate_id field because it is in the `SET` part of the - -- query built by the DAO, but in C*, one cannot change a value + -- certificate_id), and hence, we cannot update the + -- certificate_id field because it is in the `SET` part of the + -- query built by the db, but in C*, one cannot change a value -- from the clustering key. - local ssl_certificate_2 = assert(dao.ssl_certificates:insert { + local certificate_2 = bp.certificates:insert { cert = "foo", key = "bar", - }) + } - local res = assert(client:send { - method = "PATCH", - path = "/snis/foo.com", + local res = client:patch("/server_names/foo.com", { body = { - ssl_certificate_id = ssl_certificate_2.id, + certificate = { id = certificate_2.id }, }, headers = { ["Content-Type"] = "application/json" }, }) local body = assert.res_status(200, res) local json = cjson.decode(body) - assert.equal(ssl_certificate_2.id, json.ssl_certificate_id) + assert.equal(certificate_2.id, json.certificate.id) end) end end) describe("DELETE", function() - it("deletes a SNI", function() - local res = assert(client:send { - method = "DELETE", - path = "/snis/foo.com", - }) - + it("deletes a Server Name", function() + local res = client:delete("/server_names/foo.com") assert.res_status(204, res) end) end) end) end) -end) +end diff --git a/spec/02-integration/05-proxy/05-ssl_spec.lua b/spec/02-integration/05-proxy/05-ssl_spec.lua index 84a6bdf023ab..0b75dc491050 100644 --- a/spec/02-integration/05-proxy/05-ssl_spec.lua +++ b/spec/02-integration/05-proxy/05-ssl_spec.lua @@ -99,7 +99,7 @@ for _, strategy in helpers.each_strategy() do body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "example.com,ssl1.com", + server_names = "example.com,ssl1.com", }, headers = { ["Content-Type"] = "application/json" }, }) diff --git a/spec/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua b/spec/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua index ebc0fb9cbfce..b0d469576fea 100644 --- a/spec/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua +++ b/spec/02-integration/06-invalidations/02-core_entities_invalidations_spec.lua @@ -373,9 +373,9 @@ for _, strategy in helpers.each_strategy() do -- ssl_certificates ------------------- - describe("ssl_certificates / SNIs", function() + describe("ssl_certificates / Server names", function() - local function get_cert(port, sni) + local function get_cert(port, sn) local pl_utils = require "pl.utils" local cmd = [[ @@ -385,7 +385,7 @@ for _, strategy in helpers.each_strategy() do -servername %s \ ]] - local _, _, stderr = pl_utils.executeex(string.format(cmd, port, sni)) + local _, _, stderr = pl_utils.executeex(string.format(cmd, port, sn)) return stderr end @@ -402,18 +402,14 @@ for _, strategy in helpers.each_strategy() do assert.matches("CN=localhost", cert_2, nil, true) end) - it("on certificate+SNI create", function() - local admin_res = assert(admin_client_1:send { - method = "POST", - path = "/certificates", + it("on certificate+Server name create", function() + local admin_res = admin_client_1:post("/certificates", { body = { cert = ssl_fixtures.cert, key = ssl_fixtures.key, - snis = "ssl-example.com", + server_names = "ssl-example.com", }, - headers = { - ["Content-Type"] = "application/json", - } + headers = { ["Content-Type"] = "application/json" } }) assert.res_status(201, admin_res) @@ -430,23 +426,18 @@ for _, strategy in helpers.each_strategy() do end) it("on certificate delete+re-creation", function() - -- TODO: PATCH/PUT update are currently not possible - -- with the admin API because snis have their name as their + -- TODO: PATCH update are currently not possible + -- with the admin API because server names have their name as their -- primary key and the DAO has limited support for such updates. - local admin_res = assert(admin_client_1:send { - method = "DELETE", - path = "/certificates/ssl-example.com", - }) + local admin_res = admin_client_1:delete("/certificates/ssl-example.com") assert.res_status(204, admin_res) - local admin_res = assert(admin_client_1:send { - method = "POST", - path = "/certificates", + local admin_res = admin_client_1:post("/certificates", { body = { - cert = ssl_fixtures.cert, - key = ssl_fixtures.key, - snis = "new-ssl-example.com", + cert = ssl_fixtures.cert, + key = ssl_fixtures.key, + server_names = "new-ssl-example.com", }, headers = { ["Content-Type"] = "application/json", @@ -474,7 +465,7 @@ for _, strategy in helpers.each_strategy() do it("on certificate update", function() -- update our certificate *without* updating the - -- attached SNI + -- attached server name local admin_res = assert(admin_client_1:send { method = "PATCH", @@ -501,21 +492,21 @@ for _, strategy in helpers.each_strategy() do assert.matches("CN=ssl-alt.com", cert_2, nil, true) end) - pending("on SNI update", function() - -- Pending: currently, SNIs cannot be updated: + pending("on server name update", function() + -- Pending: currently, server names cannot be updated: -- - A PATCH updating the name property would not work, since -- the URI path expects the current name, and so does the -- query fetchign the row to be updated -- -- -- - -- update our SNI but leave certificate untouched + -- update our Server name but leave certificate untouched local admin_res = assert(admin_client_1:send { method = "PATCH", - path = "/snis/new-ssl-example.com", + path = "/server_names/new-ssl-example.com", body = { - name = "updated-sni.com", + name = "updated-sn.com", }, headers = { ["Content-Type"] = "application/json", @@ -526,11 +517,11 @@ for _, strategy in helpers.each_strategy() do -- no need to wait for workers propagation (lua-resty-worker-events) -- because our test instance only has 1 worker - local cert_1_old_sni = get_cert(8443, "new-ssl-example.com") - assert.matches("CN=localhost", cert_1_old_sni, nil, true) + local cert_1_old_sn = get_cert(8443, "new-ssl-example.com") + assert.matches("CN=localhost", cert_1_old_sn, nil, true) - local cert_1_new_sni = get_cert(8443, "updated-sni.com") - assert.matches("CN=updated-sni.com", cert_1_new_sni, nil, true) + local cert_1_new_sn = get_cert(8443, "updated-sn.com") + assert.matches("CN=updated-sn.com", cert_1_new_sn, nil, true) end) it("on certificate delete", function() diff --git a/spec/fixtures/blueprints.lua b/spec/fixtures/blueprints.lua index 726bd7919860..bc058957d2f6 100644 --- a/spec/fixtures/blueprints.lua +++ b/spec/fixtures/blueprints.lua @@ -65,14 +65,15 @@ local _M = {} function _M.new(dao, db) local res = {} - local ssl_name_seq = new_sequence("ssl-server-%d") - res.ssl_servers_names = new_blueprint(dao.ssl_servers_names, function() + local server_name_seq = new_sequence("server-name-%d") + res.server_names = new_blueprint(db.server_names, function(overrides) return { - name = ssl_name_seq:next(), + name = server_name_seq:next(), + certificate = overrides.certificate or res.certificates:insert(), } end) - res.ssl_certificates = new_blueprint(dao.ssl_certificates, function() + res.certificates = new_blueprint(db.certificates, function() return { cert = ssl_fixtures.cert, key = ssl_fixtures.key,