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(cp): add dp cert details #11921

Merged
merged 15 commits into from
Nov 16, 2023
5 changes: 5 additions & 0 deletions changelog/unreleased/kong/cp-expose-dp-cert-details.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
message: |
**Clustering**: Expose data plane certificate details on the control plane API.
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
type: feature
scope: Clustering

1 change: 1 addition & 0 deletions kong-3.6.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ build = {
["kong.db.migrations.core.019_320_to_330"] = "kong/db/migrations/core/019_320_to_330.lua",
["kong.db.migrations.core.020_330_to_340"] = "kong/db/migrations/core/020_330_to_340.lua",
["kong.db.migrations.core.021_340_to_350"] = "kong/db/migrations/core/021_340_to_350.lua",
["kong.db.migrations.core.022_350_to_360"] = "kong/db/migrations/core/022_350_to_360.lua",
["kong.db.migrations.operations.200_to_210"] = "kong/db/migrations/operations/200_to_210.lua",
["kong.db.migrations.operations.212_to_213"] = "kong/db/migrations/operations/212_to_213.lua",
["kong.db.migrations.operations.280_to_300"] = "kong/db/migrations/operations/280_to_300.lua",
Expand Down
3 changes: 3 additions & 0 deletions kong/clustering/control_plane.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +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
Expand Down Expand Up @@ -220,6 +221,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)
flrgh marked this conversation as resolved.
Show resolved Hide resolved
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 +237,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)
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
local cert, err = openssl_x509.new(dp_cert_pem, "PEM")
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
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 }
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
},
description = "Certificate details of the data plane.",
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
}
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()
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
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, }
})
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
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"))
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
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)