Skip to content

Commit

Permalink
feat(cp): add dp cert details
Browse files Browse the repository at this point in the history
support for exposing dataplane certificate expiry date to
`/clustering/data-planes` endpoint

Fix: [FTI-5530](https://konghq.atlassian.net/browse/FTI-5530)
Signed-off-by: tzssangglass <[email protected]>
  • Loading branch information
tzssangglass committed Nov 6, 2023
1 parent b3851a6 commit a929eb0
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 1 deletion.
4 changes: 3 additions & 1 deletion kong/clustering/control_plane.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local compat = require("kong.clustering.compat")
local constants = require("kong.constants")
local events = require("kong.clustering.events")
local calculate_config_hash = require("kong.clustering.config_helper").calculate_config_hash

local extract_dp_cert = require("kong.clustering.tls").extract_dp_cert

local string = string
local setmetatable = setmetatable
Expand Down Expand Up @@ -220,6 +220,7 @@ function _M:handle_cp_websocket()
return ngx_exit(ngx_CLOSE)
end

local dp_cert_details = extract_dp_cert(ngx_var.ssl_client_raw_cert)
local dp_plugins_map = plugins_list_to_map(data.plugins)
local config_hash = DECLARATIVE_EMPTY_CONFIG_HASH -- initial hash
local last_seen = ngx_time()
Expand All @@ -235,6 +236,7 @@ function _M:handle_cp_websocket()
version = dp_version,
sync_status = sync_status, -- TODO: import may have been failed though
labels = data.labels,
cert_details = dp_cert_details,
}, { ttl = purge_delay })
if not ok then
ngx_log(ngx_ERR, _log_prefix, "unable to update clustering data plane status: ", err, log_suffix)
Expand Down
23 changes: 23 additions & 0 deletions kong/clustering/tls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ local constants = require("kong.constants")

local ngx_log = ngx.log
local WARN = ngx.WARN
local tostring = tostring


local OCSP_TIMEOUT = constants.CLUSTERING_OCSP_TIMEOUT

Expand Down Expand Up @@ -230,4 +232,25 @@ function tls.validate_client_cert(kong_config, cp_cert, dp_cert_pem)
end


--- Extract certificate details from the data plane certificate.
---
---@param dp_cert_pem string # data plane cert text
---
---@return table? cert_details # certificate details
function tls.extract_dp_cert(dp_cert_pem)
local cert, err = openssl_x509.new(dp_cert_pem, "PEM")
if not cert then
return nil, "unable to load data plane client certificate during connection established: " .. err
end

local expiry_timestamp = cert:get_not_after()
-- values in cert_details must be strings
local cert_details = {
expiry_timestamp = expiry_timestamp,
}

return cert_details
end


return tls
13 changes: 13 additions & 0 deletions kong/db/migrations/core/022_350_to_360.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
return {
postgres = {
up = [[
DO $$
BEGIN
ALTER TABLE IF EXISTS ONLY "clustering_data_planes" ADD "cert_details" JSONB;
EXCEPTION WHEN DUPLICATE_COLUMN THEN
-- Do nothing, accept existing state
END;
$$;
]]
}
}
1 change: 1 addition & 0 deletions kong/db/migrations/core/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ return {
"019_320_to_330",
"020_330_to_340",
"021_340_to_350",
"022_350_to_360",
}
8 changes: 8 additions & 0 deletions kong/db/schema/entities/clustering_data_planes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,13 @@ return {
description = "Custom key value pairs as meta-data for DPs.",
},
},
{ cert_details = {
type = "record",
fields = {
{ expiry_timestamp = typedefs.auto_timestamp_s }
},
description = "Certificate details of the data plane.",
},
},
},
}
12 changes: 12 additions & 0 deletions spec/01-unit/01-db/01-schema/13-cluster_status_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,16 @@ describe("plugins", function()
assert.is_true(ok)
assert.is_nil(err)
end)

it("accepts cetr details", function()
local ok, err = validate({
ip = "127.0.0.1",
hostname = "dp.example.com",
cert_details = {
expiry_timestamp = 1897136778,
}
})
assert.is_true(ok)
assert.is_nil(err)
end)
end)
26 changes: 26 additions & 0 deletions spec/02-integration/03-db/13-cluster_status_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,31 @@ for _, strategy in helpers.each_strategy() do
assert.is_nil(err)
end)
end)

describe("cert_details", function()
it(":upsert()", function()
local p, err = db.clustering_data_planes:upsert({ id = "eb51145a-aaaa-bbbb-cccc-22087fb081db", },
{ config_hash = "a9a166c59873245db8f1a747ba9a80a7",
hostname = "localhost",
ip = "127.0.0.1",
cert_details = {
expiry_timestamp = 1897136778,
}
})

assert.is_truthy(p)
assert.is_nil(err)
end)

it(":update()", function()
-- this time update instead of insert
local p, err = db.clustering_data_planes:update({ id = "eb51145a-aaaa-bbbb-cccc-22087fb081db", },
{ config_hash = "a9a166c59873245db8f1a747ba9a80a7",
cert_details = { expiry_timestamp = 1888983905, }
})
assert.is_truthy(p)
assert.is_nil(err)
end)
end)
end) -- kong.db [strategy]
end
116 changes: 116 additions & 0 deletions spec/02-integration/09-hybrid_mode/01-sync_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -784,4 +784,120 @@ describe("CP/DP labels #" .. strategy, function()
end)
end)

describe("CP/DP cert details(cluster_mtls = shared) #" .. strategy, function()
lazy_setup(function()
helpers.get_db_utils(strategy) -- runs migrations

assert(helpers.start_kong({
role = "control_plane",
cluster_cert = "spec/fixtures/kong_clustering.crt",
cluster_cert_key = "spec/fixtures/kong_clustering.key",
database = strategy,
db_update_frequency = 0.1,
cluster_listen = "127.0.0.1:9005",
nginx_conf = "spec/fixtures/custom_nginx.template",
}))

assert(helpers.start_kong({
role = "data_plane",
database = "off",
prefix = "servroot2",
cluster_cert = "spec/fixtures/kong_clustering.crt",
cluster_cert_key = "spec/fixtures/kong_clustering.key",
cluster_control_plane = "127.0.0.1:9005",
proxy_listen = "0.0.0.0:9002",
nginx_conf = "spec/fixtures/custom_nginx.template",
cluster_dp_labels="deployment:mycloud,region:us-east-1",
}))
end)

lazy_teardown(function()
helpers.stop_kong("servroot2")
helpers.stop_kong()
end)

describe("status API", function()
it("shows DP cert details", function()
helpers.wait_until(function()
local admin_client = helpers.admin_client()
finally(function()
admin_client:close()
end)

local res = assert(admin_client:get("/clustering/data-planes"))
local body = assert.res_status(200, res)
local json = cjson.decode(body)

for _, v in pairs(json.data) do
if v.ip == "127.0.0.1" then
assert.equal(1888983905, v.cert_details.expiry_timestamp)
return true
end
end
end, 3)
end)
end)
end)

describe("CP/DP cert details(cluster_mtls = pki) #" .. strategy, function()
lazy_setup(function()
helpers.get_db_utils(strategy) -- runs migrations

assert(helpers.start_kong({
role = "control_plane",
cluster_cert = "spec/fixtures/kong_clustering.crt",
cluster_cert_key = "spec/fixtures/kong_clustering.key",
db_update_frequency = 0.1,
database = strategy,
cluster_listen = "127.0.0.1:9005",
nginx_conf = "spec/fixtures/custom_nginx.template",
-- additional attributes for PKI:
cluster_mtls = "pki",
cluster_ca_cert = "spec/fixtures/kong_clustering_ca.crt",
}))

assert(helpers.start_kong({
role = "data_plane",
nginx_conf = "spec/fixtures/custom_nginx.template",
database = "off",
prefix = "servroot2",
cluster_cert = "spec/fixtures/kong_clustering_client.crt",
cluster_cert_key = "spec/fixtures/kong_clustering_client.key",
cluster_control_plane = "127.0.0.1:9005",
proxy_listen = "0.0.0.0:9002",
-- additional attributes for PKI:
cluster_mtls = "pki",
cluster_server_name = "kong_clustering",
cluster_ca_cert = "spec/fixtures/kong_clustering.crt",
}))
end)

lazy_teardown(function()
helpers.stop_kong("servroot2")
helpers.stop_kong()
end)

describe("status API", function()
it("shows DP cert details", function()
helpers.wait_until(function()
local admin_client = helpers.admin_client()
finally(function()
admin_client:close()
end)

local res = assert(admin_client:get("/clustering/data-planes"))
local body = assert.res_status(200, res)
local json = cjson.decode(body)

for _, v in pairs(json.data) do
if v.ip == "127.0.0.1" then
assert.equal(1897136778, v.cert_details.expiry_timestamp)
return true
end
end
end, 3)
end)
end)
end)

end
7 changes: 7 additions & 0 deletions spec/05-migration/db/migrations/core/022_350_to_360_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
local uh = require "spec/upgrade_helpers"

describe("database migration", function()
uh.old_after_up("has created the expected new columns", function()
assert.table_has_column("clustering_data_planes", "cert_details", "jsonb")
end)
end)

0 comments on commit a929eb0

Please sign in to comment.