Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dao) move ssl certificates/snis to new DAO #3386

Merged
merged 1 commit into from
May 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions kong-0.13.1-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,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",
Expand All @@ -127,9 +125,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.snis"] = "kong/db/dao/snis.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.snis"] = "kong/db/schema/entities/snis.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",
Expand Down
3 changes: 1 addition & 2 deletions kong/api/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,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
Expand Down
323 changes: 25 additions & 298 deletions kong/api/routes/certificates.lua
Original file line number Diff line number Diff line change
@@ -1,317 +1,44 @@
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 responses = require "kong.tools.responses"


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
local function get_cert_id_from_sni(self, db, helpers)
local id = self.params.certificates
if not utils.is_valid_uuid(id) then
local sni, _, err_t = db.snis:select_by_name(id)
if err_t then
return endpoints.handle_error(err_t)
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)
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
if not sni then
responses.send_HTTP_NOT_FOUND("SNI not found")
end

return helpers.responses.send_HTTP_OK(ssl_cert)
self.params.certificates = sni.certificate.id
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
end

return helpers.responses.send_HTTP_OK(ssl_cert)
end


return {
["/certificates/"] = {
POST = function(self, dao_factory, helpers)
create_certificate(self, dao_factory, helpers)
end,
["/certificates/:certificates"] = {
before = get_cert_id_from_sni,

-- override to include the snis list when getting an individual certificate
GET = function(self, db, helpers)
local pk = { id = self.params.certificates }

GET = function(self, dao_factory, helpers)
local ssl_certificates, err = dao_factory.ssl_certificates:find_all()
if err then
return helpers.yield_error(err)
local cert, _, err_t = db.certificates:select_with_name_list(pk)
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_OK(cert)
end,
},


["/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

-- cache certificate row id

self.ssl_certificate_id = row.ssl_certificate_id
end

self.params.sni_or_uuid = nil
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)
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)
end,


DELETE = function(self, dao_factory, helpers)
return crud.delete({
id = self.ssl_certificate_id
}, dao_factory.ssl_certificates)
end,
}
["/certificates/:certificates/snis"] = {
before = get_cert_id_from_sni,
},
}

Loading