-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: move ee tls.plugin dir to ce code
- Loading branch information
Showing
2 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
-- This software is copyright Kong Inc. and its licensors. | ||
-- Use of the software is subject to the agreement between your organization | ||
-- and Kong Inc. If there is no such agreement, use is governed by and | ||
-- subject to the terms of the Kong Master Software License Agreement found | ||
-- at https://konghq.com/enterprisesoftwarelicense/. | ||
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ] | ||
|
||
--- Copyright 2019 Kong Inc. | ||
local ngx_ssl = require "ngx.ssl" | ||
local sni_filter = require("kong.tls.plugins.sni_filter") | ||
local pl_stringx = require "pl.stringx" | ||
local server_name = ngx_ssl.server_name | ||
local PREFIX_SNIS_PSEUDO_INDEX = sni_filter.PREFIX_SNIS_PSEUDO_INDEX | ||
local POSTFIX_SNIS_PSEUDO_INDEX = sni_filter.POSTFIX_SNIS_PSEUDO_INDEX | ||
local startswith = pl_stringx.startswith | ||
local endswith = pl_stringx.endswith | ||
|
||
local _M = {} | ||
|
||
local kong = kong | ||
local EMPTY_T = {} | ||
|
||
|
||
local function match_sni(snis, server_name) | ||
local matched_sni | ||
if server_name then | ||
-- search plain snis | ||
if snis[server_name] then | ||
kong.log.debug("matched the plain sni ", server_name) | ||
return snis[server_name] | ||
end | ||
|
||
-- TODO: use radix tree to accelerate the search once we have an available implementation | ||
-- search snis with the leftmost wildcard | ||
for sni, sni_t in pairs(snis[POSTFIX_SNIS_PSEUDO_INDEX] or EMPTY_T) do | ||
if endswith(server_name, sni_t.value) then | ||
kong.log.debug(server_name, " matched the sni with the leftmost wildcard ", sni) | ||
return sni_t | ||
end | ||
end | ||
|
||
-- search snis with the rightmost wildcard | ||
for sni, sni_t in pairs(snis[PREFIX_SNIS_PSEUDO_INDEX] or EMPTY_T) do | ||
if startswith(server_name, sni_t.value) then | ||
kong.log.debug(server_name, " matched the sni with the rightmost wildcard ", sni) | ||
return sni_t | ||
end | ||
end | ||
end | ||
|
||
if server_name then | ||
kong.log.debug("client sent an unknown sni ", server_name) | ||
|
||
else | ||
kong.log.debug("client didn't send an sni") | ||
end | ||
|
||
if snis["*"] then | ||
kong.log.debug("mTLS is enabled globally") | ||
return snis["*"] | ||
end | ||
end | ||
|
||
function _M.execute(snis_set) | ||
|
||
local server_name = server_name() | ||
|
||
local sni_mapping = match_sni(snis_set, server_name) | ||
|
||
if sni_mapping then | ||
-- TODO: improve detection of ennoblement once we have DAO functions | ||
-- to filter plugin configurations based on plugin name | ||
|
||
kong.log.debug("enabled, will request certificate from client") | ||
|
||
local chain | ||
-- send CA DN list | ||
if sni_mapping.ca_cert_chain then | ||
kong.log.debug("set client ca certificate chain") | ||
chain = sni_mapping.ca_cert_chain.ctx | ||
end | ||
|
||
local res, err = kong.client.tls.request_client_certificate(chain) | ||
if not res then | ||
kong.log.err("unable to request client to present its certificate: ", | ||
err) | ||
end | ||
|
||
-- disable session resumption to prevent inability to access client | ||
-- certificate in later phases | ||
res, err = kong.client.tls.disable_session_reuse() | ||
if not res then | ||
kong.log.err("unable to disable session reuse for client certificate: ", | ||
err) | ||
end | ||
end | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
-- This software is copyright Kong Inc. and its licensors. | ||
-- Use of the software is subject to the agreement between your organization | ||
-- and Kong Inc. If there is no such agreement, use is governed by and | ||
-- subject to the terms of the Kong Master Software License Agreement found | ||
-- at https://konghq.com/enterprisesoftwarelicense/. | ||
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ] | ||
|
||
local constants = require "kong.constants" | ||
local openssl_x509 = require "resty.openssl.x509" | ||
local chain_lib = require "resty.openssl.x509.chain" | ||
|
||
local _M = {} | ||
|
||
local kong = kong | ||
local null = ngx.null | ||
local ipairs = ipairs | ||
local new_tab = require("table.new") | ||
|
||
local PREFIX_SNIS_PSEUDO_INDEX = -1 | ||
local POSTFIX_SNIS_PSEUDO_INDEX = -2 | ||
_M.PREFIX_SNIS_PSEUDO_INDEX = PREFIX_SNIS_PSEUDO_INDEX | ||
_M.POSTFIX_SNIS_PSEUDO_INDEX = POSTFIX_SNIS_PSEUDO_INDEX | ||
local TTL_FOREVER = { ttl = 0 } | ||
|
||
local ca_cert_cache_opts = { | ||
l1_serializer = function(ca) | ||
local x509, err = openssl_x509.new(ca.cert, "PEM") | ||
if err then | ||
return nil, err | ||
end | ||
|
||
return x509 | ||
end | ||
} | ||
|
||
|
||
-- make the table out side of function to reuse table | ||
local key = new_tab(1, 0) | ||
|
||
local function load_ca(ca_id) | ||
kong.log.debug("cache miss for CA Cert") | ||
|
||
key.id = ca_id | ||
local ca, err = kong.db.ca_certificates:select(key) | ||
if not ca then | ||
if err then | ||
return nil, err | ||
end | ||
|
||
return nil, "CA Certificate '" .. tostring(ca_id) .. "' does not exist" | ||
end | ||
|
||
return ca | ||
end | ||
|
||
local function merge_ca_ids(sni, ca_ids) | ||
sni.ca_ids = sni.ca_ids or {} | ||
local sni_ca_ids = sni.ca_ids | ||
|
||
for _, ca_id in ipairs(ca_ids) do | ||
if not sni_ca_ids[ca_id] then | ||
sni_ca_ids[ca_id] = true | ||
end | ||
end | ||
end | ||
|
||
local function ca_cert_cache_key(ca_id) | ||
return "mtls:cacert:" .. ca_id | ||
end | ||
|
||
local function load_routes_from_db(db, route_id, options) | ||
kong.log.debug("cache miss for route id: " .. route_id.id) | ||
local routes, err = db.routes:select(route_id, options) | ||
if routes == nil then | ||
-- the third value means "do not cache" | ||
return nil, err, -1 | ||
end | ||
|
||
return routes | ||
end | ||
|
||
|
||
local function build_snis_for_route(route, snis, send_ca_dn, ca_ids) | ||
-- every route should have SNI or ask cert on all requests | ||
if not route.snis or #route.snis == 0 then | ||
snis["*"] = snis["*"] or {} | ||
|
||
if send_ca_dn then | ||
merge_ca_ids(snis["*"], ca_ids) | ||
end | ||
|
||
else | ||
for _, sni in ipairs(route.snis) do | ||
local sni_t | ||
local idx = sni:find("*", 1, true) | ||
|
||
if idx == 1 then | ||
-- store snis with the leftmost wildcard in a subtable | ||
snis[POSTFIX_SNIS_PSEUDO_INDEX] = snis[POSTFIX_SNIS_PSEUDO_INDEX] or {} | ||
local postfix_snis = snis[POSTFIX_SNIS_PSEUDO_INDEX] | ||
postfix_snis[sni] = postfix_snis[sni] or { value = sni:sub(2) } | ||
sni_t = postfix_snis[sni] | ||
kong.log.debug("add a postfix sni ", sni) | ||
|
||
elseif idx == #sni then | ||
-- store snis with the rightmost wildcard in a subtable | ||
snis[PREFIX_SNIS_PSEUDO_INDEX] = snis[PREFIX_SNIS_PSEUDO_INDEX] or {} | ||
local prefix_snis = snis[PREFIX_SNIS_PSEUDO_INDEX] | ||
prefix_snis[sni] = prefix_snis[sni] or { value = sni:sub(1, -2) } | ||
sni_t = prefix_snis[sni] | ||
kong.log.debug("add a prefix sni ", sni) | ||
|
||
else | ||
snis[sni] = snis[sni] or {} | ||
sni_t = snis[sni] | ||
kong.log.debug("add a plain sni ", sni) | ||
end | ||
|
||
if send_ca_dn then | ||
merge_ca_ids(sni_t, ca_ids) | ||
end | ||
end | ||
end | ||
end | ||
|
||
|
||
local function get_snis_for_plugin(db, plugin, snis, options) | ||
-- plugin applied on service | ||
local service_pk = plugin.service | ||
local send_ca_dn = plugin.config.send_ca_dn | ||
local ca_ids = plugin.config.ca_certificates | ||
|
||
if service_pk then | ||
for route, err in db.routes:each_for_service(service_pk, nil, options) do | ||
if err then | ||
return err | ||
end | ||
|
||
-- XXX: strictly speaking, if a mtls plugin is also applied on the route, | ||
-- then we should skip the plugin applied on the corresponding service, | ||
-- as the plugin on route has a higher priority. | ||
-- But this requires a plugin iteration on every route. | ||
-- For performance considerations, we choose to continue. | ||
-- Sending a few more ca dn is not a big deal, since we are already doing | ||
-- this by merging the ca dn of mtls plugins with the same sni. | ||
-- After all, sending some extra ca dn is better than sending nothing. | ||
build_snis_for_route(route, snis, send_ca_dn, ca_ids) | ||
end | ||
|
||
return | ||
end | ||
|
||
-- plugin applied on route | ||
local route_pk = plugin.route | ||
if route_pk then | ||
-- since routes entity is workspaceable, workspace id | ||
-- needs to be passed when computing cache key | ||
local cache_key = db.routes:cache_key(route_pk.id, nil, nil, nil, nil, plugin.ws_id) | ||
local cache_obj = kong[constants.ENTITY_CACHE_STORE.routes] | ||
local route, err = cache_obj:get(cache_key, TTL_FOREVER, | ||
load_routes_from_db, db, | ||
route_pk, options) | ||
|
||
if err then | ||
return err | ||
end | ||
|
||
build_snis_for_route(route, snis, send_ca_dn, ca_ids) | ||
|
||
return | ||
end | ||
|
||
-- plugin applied on global scope | ||
snis["*"] = snis["*"] or {} | ||
if send_ca_dn then | ||
merge_ca_ids(snis["*"], ca_ids) | ||
end | ||
end | ||
|
||
-- build ca_cert_chain from sni_t | ||
local function build_ca_cert_chain(sni_t) | ||
local ca_ids = sni_t.ca_ids | ||
|
||
if not ca_ids then | ||
return true | ||
end | ||
|
||
local chain, err = chain_lib.new() | ||
if err then | ||
return nil, err | ||
end | ||
|
||
for ca_id, _ in pairs(ca_ids) do | ||
local x509, err = kong.cache:get(ca_cert_cache_key(ca_id), ca_cert_cache_opts, | ||
load_ca, ca_id) | ||
if err then | ||
return nil, err | ||
end | ||
|
||
local _ | ||
_, err = chain:add(x509) | ||
|
||
if err then | ||
return nil, err | ||
end | ||
end | ||
|
||
sni_t.ca_cert_chain = chain | ||
|
||
return true | ||
end | ||
|
||
|
||
-- build ca_cert_chain for every sni | ||
function _M.sni_cache_l1_serializer(snis) | ||
for k, v in pairs(snis) do | ||
if k == PREFIX_SNIS_PSEUDO_INDEX or | ||
k == POSTFIX_SNIS_PSEUDO_INDEX then | ||
for _, sni_t in pairs(v) do | ||
local res, err = build_ca_cert_chain(sni_t) | ||
if not res then | ||
return nil, err | ||
end | ||
end | ||
|
||
else | ||
local res, err = build_ca_cert_chain(v) | ||
if not res then | ||
return nil, err | ||
end | ||
end | ||
end | ||
|
||
return snis | ||
end | ||
|
||
local function each_enabled_plugin(entity, plugin_name) | ||
local options = { | ||
show_ws_id = true, | ||
workspace = null, | ||
search_fields = { | ||
name = { eq = plugin_name }, | ||
enabled = { eq = true } | ||
} | ||
} | ||
|
||
local iter = entity:each(1000, options) | ||
local function iterator() | ||
local element, err = iter() | ||
if err then return nil, err end | ||
if element == nil then return end | ||
-- XXX | ||
-- `search_fields` is PostgreSQL-backed instances only. | ||
-- We also need a backstop here for Cassandra or DBless. | ||
if element.name == plugin_name and element.enabled then return element, nil end | ||
return iterator() | ||
end | ||
|
||
return iterator | ||
end | ||
|
||
function _M.build_ssl_route_filter_set(plugin_name) | ||
kong.log.debug("building ssl route filter set for plugin name " .. plugin_name) | ||
local db = kong.db | ||
local snis = {} | ||
|
||
local options = { workspace = null } | ||
for plugin, err in each_enabled_plugin(db.plugins, plugin_name) do | ||
if err then | ||
return nil, "could not load plugins: " .. err | ||
end | ||
|
||
local err = get_snis_for_plugin(db, plugin, snis, options) | ||
if err then | ||
return nil, err | ||
end | ||
end | ||
|
||
return snis | ||
end | ||
|
||
|
||
return _M |