-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is very invasive PR. It kinda points shortcomings in customizations. It is rhuge PR, KUDOS to that huge amount of work, also makes reviewing hard.
- It adds new migrations (both code and sql/cql), and even new quite big migration tools
- It renames entities
- It adds custom daos for both entities it adds, and a new field named
super
- It adds custom routes for both entities it adds
- It adds two new error codes in endpoints
I kinda think this is good first step, but we should really work on merging the certificates
and server_names
. And then I think introducing snis
rename to server_names
just makes another breaking change here. First step, just transfer entities to new, and try not to break too much if they were in old too. Next step, refactor, merge, redesign it. Is that needed for safer migrations or something like that?
kong/api/routes/certificates.lua
Outdated
local utils = require "kong.tools.utils" | ||
local cjson = require "cjson" | ||
local endpoints = require "kong.api.endpoints" | ||
local utils = require "kong.tools.utils" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more new line here.
kong/api/routes/certificates.lua
Outdated
|
||
local function get_cert_by_server_name_or_id(self, db, helpers) | ||
local id = self.params.certificates | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New line maybe not needed, similar to pattern:
local ok, err = func()
if not ok then
end
E.g. where test follows immediately the declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done
kong/api/routes/server_names.lua
Outdated
-- GET / PATCH / DELETE /server_names/server_name are the only methods allowed | ||
|
||
["/server_names"] = { | ||
before = method_not_allowed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are not allowed, is 404
a better one? E.g. is there any endpoint /server_names
that you can call with any method? If not with any, then I would go with 404
. We could also add some support for autogenerator to remove endpoints, but not in this PR.
kong/api/routes/server_names.lua
Outdated
}, | ||
|
||
["/server_names/:server_names/certificate"] = { | ||
before = method_not_allowed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, if this is the only:
/server_names/server_name
Endpoint, maybe we don't want to give reply that says that it is not allowed, users might try then other methods, and these seems no method allowed here. So I think it would be better to just have 404
.
kong/dao/migrations/postgres.lua
Outdated
local sql = fmt("UPDATE server_names SET id = '%s' WHERE name = '%s';", | ||
utils.uuid(), | ||
row.name) | ||
local _, err = dao.db:query(sql) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of executing these one-by-one, could up make up an transaction and do them in one query
that is wrapped in transaction?
kong/db/dao/certificates.lua
Outdated
if type(input) == "string" then | ||
name_list = utils.split(input, ",") | ||
elseif type(input) == "table" then | ||
name_list = utils.shallow_copy(input) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line after this
kong/db/dao/certificates.lua
Outdated
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leads to interesting situations, like certificate added, but no server_names added. Do we have any concerns of that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not have that situation, but I honestly didn't know what else to do (we don't have transactions at this level, because Cassandra).
This code reflects what the old DAO code did - it makes as much "checks" as possible before writing anything to the db.
kong/db/dao/init.lua
Outdated
@@ -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, ...) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
kong/db/dao/server_names.lua
Outdated
return nil, err, err_t | ||
end | ||
if row and row.certificate.id ~= cert_pk.id then | ||
local msg = "Server Name '" .. row.name .. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On line 47 it says "Server name already exists
and here Server Name
. I think in general these messages should be all lower case (because they are injected usually in different places), comments on that?
kong/db/init.lua
Outdated
if not ok then | ||
return nil, fmt("schema of entity '%s' is invalid: %s", entity_name, | ||
err) | ||
pl_pretty.write(err_t)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this, or was this for debugging?
a888538
to
db91399
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
39f7351
to
0e5802d
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of review for me!
kong/db/dao/certificates.lua
Outdated
local function parse_name_list(input, errors) | ||
local name_list | ||
if type(input) == "string" then | ||
name_list = utils.split(input, ",") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole point of the new Admin API endpoints is to get rid of comma-separated strings and use the new arg[]=
notation instead (for form-encoded payloads). Why supporting comma-separated lists then?
kong/db/dao/certificates.lua
Outdated
end | ||
|
||
cert.snis = nil | ||
cert, err, err_t = assert(self:insert(cert)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert()
in a client-facing code path seems like this is a leftover or something?
kong/db/errors.lua
Outdated
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks conflicting with the above SCHEMA_VIOLATION
error (as in, it basically falls under a subcategory of schema violations). I also dislike that this error producer accepts any error message, thus can throw any type of error and is up to interpretation... All errors produced in this file (except the database-ones) have specific, precise error messages. The goal being to avoid the proliferation and possibly, duplication of similar error types.
Any thought on this @hishamhm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks conflicting with the above
SCHEMA_VIOLATION
error
I have mixed feelings about reusing the error messages for errors detected via different sources.
On the one hand, for the user perspective I agree that the way bad input parameters were caught shouldn't affect the error message, it's an implementation detail. On the other hand, debugging gets harder when we get reports of SCHEMA_VIOLATION
from the field and these don't come from the schema library.
I don't think it's the case for certificates, but if something like this starts happening in other parts of the code it could easily trip up plugin authors ("but my schema looks okay!"). But as you said, certs/snis are "abnormal" in that sense, so it's probably fine to make an exception for it in the name of better user-visible consistency.
In that case, the SCHEMA_VIOLATION
should be formatted in a way consistent with those produced by the schema library (e.g. listing the offending fields, etc).
I also dislike that this error producer accepts any error message
Definitely agree with this. It shouldn't just forward the full error message, it should construct a message like the other functions in this module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed both my custom errors and used schema violations everywhere. All the errors happen on the context of the snis
magical pseudo-attribute, so it does not look bad (all errors appear "scoped" by snis
).
kong/db/dao/snis.lua
Outdated
end | ||
end | ||
|
||
return 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why choosing this return value (1
) for the methods in this module instead of the conventional true
?
kong/api/routes/certificates.lua
Outdated
end, | ||
}, | ||
|
||
["/certificates/:certificates"] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:certificates
-> :certificate_id
? Or even :certificate_id_or_sni
if we allow querying by SNI as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to use what @kikito used to override autogenerated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirm that is the case. The auto-generated route is "certificates/certificates"
so I must use the same name in order to override stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is exactly the reason. The auto-generated route is /certificates/:certificates
.
We would have had to add a param called "singular_name" or something similar to the schema in order to be able to do fancy things like sometimes using singulars instead of plurals everywhere.
kong/db/dao/certificates.lua
Outdated
|
||
if not name_list then | ||
return nil | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code would be simplified if we avoided returning nil
from this function, and instead always return a table (empty if no names):
if type(input) == "table" then
name_list = utils.shallow_copy(input)
elseif input == ngx.null then
name_list = {}
else
-- maybe throw an error here, looks like a programming error
end
-- ...
return setmetatable(name_list, cjson.empty_array_mt)
This way, the below insert_with_name_list()
method could do:
if #name_list > 0 then
end
-- ...
cert.snis = name_list -- no need for cjson.empty_array fallback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea I was going for was this:
- If you don't set the
snis
variable at all, it remains as it was (no programming error, maybe you where just missing a char on the key and are updating just that). - If you set the
snis
variable, it will create/delete whatever snis is necessary. This includes passing an empty table (all snis will be removed) - If you pass a json null, it is as if you passed an empty table.
If I do the change you propose, there are two drawbacks:
- Either we consider that an empty table means "no changes" (which I think is confusing).
- Or we require the user to pass the
snis
param every time they want to make a change on a cert, even for changing the cert or key (otherwise we set of an error).
If you think this is worth it, I will implement it, but I wanted to warn you about the costs first.
end | ||
|
||
table.sort(name_list) | ||
return name_list |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
kong/db/dao/certificates.lua
Outdated
-- names associated to the cert, after being parsed by parse_name_list. | ||
-- Returns a certificate with the snis sorted alphabetically. | ||
function _Certificates:insert_with_name_list(cert) | ||
local db = singletons.db |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of my biggest concerns with this PR. We need to find a way to avoid this at all costs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem is that we have still not decided how to "refer to other tables from the DAO of one table" in the new DAO at least.
Quick solution: I think I can change kong.db.dao.init
so that db
is an attribute of self
here, so we can do self.db.snis:whatever
instead of singletons.db.snis:whatever
. Would that be ok?
kong/db/dao/init.lua
Outdated
return nil, err, err_t | ||
end | ||
if not entity then | ||
return 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our rationale was to not return the number of affected rows from delete()
(because of the lack of support from Cassandra for this). Is the rationale behind this return value to indicate that no row was deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. With the read-before-delete you know which rows will (should) be affected by a delete. The 0
means "I could not find any rows". I can replace it with a true
if you prefer.
kong/db/errors.lua
Outdated
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's maybe make the description more precise: "a conflict with already inserted DB values"?
I recall discussing this with @bungle, and since this will result from UNIQUE
constraints set in PostgreSQL, we wanted to name this value UNIQUE_VIOLATION
to stay consistent with other _VIOLATION
error types we already defined.
Since the certs/sni entities are already abnormal in the sense that they validate their own values and produce their own errors, I would rather avoid spreading that abnormality to the errors as well. So in other words, the conflict errors produced by certs/sni validations are not because of DB/schema UNIQUE constraints, but I don't think that matters and I think they can throw UNIQUE_VIOLATION
which has the same semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have chosen to use schema violations instead. The errors always happen in the context of the snis
magical meta-param, so it "fits" to just use schema violations for those (so they are scoped inside snis
).
534e33a
to
dc2a798
Compare
I made a change that makes I did not dare changing |
884e17a
to
3e09b2e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be merged on Wednesday, May 2nd. If you still want to comment, this is your final chance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few minor comments on style.
CREATE INDEX IF NOT EXISTS snis_name_idx ON snis(name); | ||
CREATE INDEX IF NOT EXISTS snis_certificate_id_idx ON snis(certificate_id); | ||
]], | ||
down = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a bit curious: assigning explicitly for style?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Salazar I made other changes to this except this. I see it is always "defined" in other places, sometimes with empty function and sometimes with nil
. Go figure. Didn't feel too bad for leaving them, :-)
kong/dao/migrations/cassandra.lua
Outdated
]], | ||
down = nil | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps no new line for consistency?
kong/dao/migrations/cassandra.lua
Outdated
down = nil | ||
}, | ||
|
||
{ name = "2018-03-26-234600_copy_records_to_new_ssl_tables", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line after opening {
kong/dao/migrations/cassandra.lua
Outdated
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps no new line for consistency?
kong/dao/migrations/helpers.lua
Outdated
@@ -5,7 +5,6 @@ local utils = require "kong.tools.utils" | |||
|
|||
local _M = {} | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: two blank lines
@@ -1,3 +1,5 @@ | |||
local utils = require "kong.tools.utils" | |||
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -0,0 +1,224 @@ | |||
local cjson = require "cjson" | |||
local utils = require "kong.tools.utils" | |||
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
With |
kong/dao/migrations/helpers.lua
Outdated
end | ||
|
||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire file has duplicate contents (open it locally on this branch) this absolutely needs to be fixed prior to the merge.
The |
where I can find the related doc? |
This PR continues #3315:
Migrations
ssl_certificates
table renamed tocertificates
ssl_servers_names
table renamed tosnis
. Added a new field (id
, UUID) and made that the PK. But also added indexes for thename
field.Admin API
/snis
is automatically generated by the new DAO admin api generator-from-schema./certificates
is also generated, but it has some overrides, to deal with three "special" things:server_names
accepts a comma-separated string or an array of strings. Itallows "setting the server names in one go" when creating (POST) or updating
(PATCH) a certificate.
server_names
attribute is automatically added to the certs when gettingeach one individually or when getting a list of them.
name
attribute ofany of its associated
server_name
s.PUT
is not allowed any more./snis
endpoints that are not needed any more:GET /snis
is not needed any more now thatGET /certificates
also returns the list of server names associated to every certPOST
/snisis not needed since
POST /certificates/id/server_names` is easier to use and does the sameGET /snis/[name]/certificate was not needed since you can do
GET /certificates/[name]` to get the same infoDELETE /snis/x
(to remove a server name quickly),PATCH /snis/x
(to move server names quickly between certificates),GET /snis/x
(for symmetry with DELETE and PATCH).DAO
ssl_certificates
and ssl_servers_names' schemas translated to new DAO's schemas (renamed tocertificates
andsnis
).dao:delete*
methods. This read was needed in order to propagate the structure through the worker events so it could be deleted properly (worker events can use any attributefrom the entitity, for example for the server names you need their
name
attribute. So if you delete a server name using itsid
you still need to read it in order to propagate an event with thename
field).certificates
has a custom DAO to deal with theserver_names
pseudo-attribute and for "looking for a certificate using one of its server names'name
"server_names
also has a custom DAO for "using the server names as if they were lists of strings", which is easier to do than "handling them as instances of theserver_names
schema".Tests
DAO:delete*
methods now returns the entity which was erased instead oftrue
, so some tests had to be changed fromis_true
tois_truthy
.PUT
where just removedRelated work
delete_by_field
methods on the strategies (we can use the PK, we have it because of the "read"). We could remove those methods.