diff --git a/kong.conf.default b/kong.conf.default index 0cbe58134f1b..27b8d1d5d1d3 100644 --- a/kong.conf.default +++ b/kong.conf.default @@ -1224,12 +1224,32 @@ # See the lua-nginx-module documentation for more information: # https://github.com/openresty/lua-nginx-module -#lua_ssl_trusted_certificate = # Absolute path to the certificate - # authority file for Lua cosockets in PEM - # format. This certificate will be the one - # used for verifying Kong's database - # connections, when `pg_ssl_verify` or - # `cassandra_ssl_verify` are enabled. + +#lua_ssl_trusted_certificate = # Comma-separated list of paths to certificate + # authority files for Lua cosockets in PEM format. + # + # The special value `system` attempts to search for the + # "usual default" provided by each distro, according + # to an arbitrary heuristic. In the current implementation, + # The following pathnames will be tested in order, + # and the first one found will be used: + # + # * /etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu/Gentoo) + # * /etc/pki/tls/certs/ca-bundle.crt (Fedora/RHEL 6) + # * /etc/ssl/ca-bundle.pem (OpenSUSE) + # * /etc/pki/tls/cacert.pem (OpenELEC) + # * /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem (CentOS/RHEL 7) + # * /etc/ssl/cert.pem (OpenBSD, Alpine) + # + # If no file is found on any of these paths, an error will + # be raised. + # + # `system` can be used by itself or in conjunction with other + # CA filepaths. + # + # When `pg_ssl_verify` or `cassandra_ssl_verify` + # are enabled, these certificate authority files will be + # used for verifying Kong's database connections. # # See https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate diff --git a/kong/cmd/utils/prefix_handler.lua b/kong/cmd/utils/prefix_handler.lua index 82bbe5033f77..3c44677ce9f8 100644 --- a/kong/cmd/utils/prefix_handler.lua +++ b/kong/cmd/utils/prefix_handler.lua @@ -103,6 +103,23 @@ local function gen_default_ssl_cert(kong_config, target) return true end + +local function gen_trusted_certs_combined_file(combined_filepath, paths) + + log.verbose("generating trusted certs combined file in ", + combined_filepath) + + local fd = assert(io.open(combined_filepath, "w")) + + for _, path in ipairs(paths) do + fd:write(pl_file.read(path)) + fd:write("\n") + end + + io.close(fd) +end + + local function get_ulimit() local ok, _, stdout, stderr = pl_utils.executeex "ulimit -n" if not ok then @@ -304,6 +321,13 @@ local function prepare_prefix(kong_config, nginx_custom_template_path) kong_config.status_ssl_cert_key = kong_config.status_ssl_cert_key_default end + if kong_config.lua_ssl_trusted_certificate_combined then + gen_trusted_certs_combined_file( + kong_config.lua_ssl_trusted_certificate_combined, + kong_config.lua_ssl_trusted_certificate + ) + end + -- check ulimit local ulimit, err = get_ulimit() if not ulimit then return nil, err diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index 6d060efa0e83..81c69bc4108d 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -566,6 +566,7 @@ local CONF_INFERENCES = { deprecated = { replacement = false } }, + lua_ssl_trusted_certificate = { typ = "array" }, lua_ssl_verify_depth = { typ = "number" }, lua_socket_pool_size = { typ = "number" }, @@ -801,11 +802,31 @@ local function check_and_infer(conf, opts) end end - if conf.lua_ssl_trusted_certificate and - not pl_path.exists(conf.lua_ssl_trusted_certificate) - then - errors[#errors + 1] = "lua_ssl_trusted_certificate: no such file at " .. - conf.lua_ssl_trusted_certificate + if conf.lua_ssl_trusted_certificate then + local new_paths = {} + + for i, path in ipairs(conf.lua_ssl_trusted_certificate) do + if path == "system" then + local system_path, err = utils.get_system_trusted_certs_filepath() + if system_path then + path = system_path + + else + errors[#errors + 1] = + "lua_ssl_trusted_certificate: unable to locate system bundle - " .. + err + end + end + + if not pl_path.exists(path) then + errors[#errors + 1] = "lua_ssl_trusted_certificate: no such file at " .. + path + end + + new_paths[i] = path + end + + conf.lua_ssl_trusted_certificate = new_paths end if conf.ssl_cipher_suite ~= "custom" then @@ -1513,9 +1534,13 @@ local function load(path, custom_conf, opts) conf.admin_ssl_cert_key = pl_path.abspath(conf.admin_ssl_cert_key) end - if conf.lua_ssl_trusted_certificate then + if conf.lua_ssl_trusted_certificate + and #conf.lua_ssl_trusted_certificate > 0 then conf.lua_ssl_trusted_certificate = - pl_path.abspath(conf.lua_ssl_trusted_certificate) + tablex.map(pl_path.abspath, conf.lua_ssl_trusted_certificate) + + conf.lua_ssl_trusted_certificate_combined = + pl_path.abspath(pl_path.join(conf.prefix, ".ca_combined")) end if conf.cluster_cert and conf.cluster_cert_key then diff --git a/kong/db/strategies/cassandra/connector.lua b/kong/db/strategies/cassandra/connector.lua index 5e74dcbcc9fc..d152f0d664a2 100644 --- a/kong/db/strategies/cassandra/connector.lua +++ b/kong/db/strategies/cassandra/connector.lua @@ -141,7 +141,7 @@ function CassandraConnector.new(kong_config) max_schema_consensus_wait = kong_config.cassandra_schema_consensus_timeout, ssl = kong_config.cassandra_ssl, verify = kong_config.cassandra_ssl_verify, - cafile = kong_config.lua_ssl_trusted_certificate, + cafile = kong_config.lua_ssl_trusted_certificate_combined, lock_timeout = 30, silent = ngx.IS_CLI, } diff --git a/kong/db/strategies/postgres/connector.lua b/kong/db/strategies/postgres/connector.lua index 41548c4f3431..c22e4c831b54 100644 --- a/kong/db/strategies/postgres/connector.lua +++ b/kong/db/strategies/postgres/connector.lua @@ -925,7 +925,7 @@ function _M.new(kong_config) schema = kong_config.pg_schema or "", ssl = kong_config.pg_ssl, ssl_verify = kong_config.pg_ssl_verify, - cafile = kong_config.lua_ssl_trusted_certificate, + cafile = kong_config.lua_ssl_trusted_certificate_combined, sem_max = kong_config.pg_max_concurrent_queries or 0, sem_timeout = (kong_config.pg_semaphore_timeout or 60000) / 1000, } @@ -962,7 +962,7 @@ function _M.new(kong_config) schema = kong_config.pg_ro_schema, ssl = kong_config.pg_ro_ssl, ssl_verify = kong_config.pg_ro_ssl_verify, - cafile = kong_config.lua_ssl_trusted_certificate, + cafile = kong_config.lua_ssl_trusted_certificate_combined, sem_max = kong_config.pg_ro_max_concurrent_queries, sem_timeout = kong_config.pg_ro_semaphore_timeout and (kong_config.pg_ro_semaphore_timeout / 1000) or nil, diff --git a/kong/templates/nginx_kong.lua b/kong/templates/nginx_kong.lua index 3e9edb2404b3..773818dea462 100644 --- a/kong/templates/nginx_kong.lua +++ b/kong/templates/nginx_kong.lua @@ -11,8 +11,8 @@ lua_socket_log_errors off; lua_max_running_timers 4096; lua_max_pending_timers 16384; lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; -> if lua_ssl_trusted_certificate then -lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}'; +> if lua_ssl_trusted_certificate_combined then +lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; > end lua_shared_dict kong 5m; diff --git a/kong/templates/nginx_kong_stream.lua b/kong/templates/nginx_kong_stream.lua index cba3a2e9a663..7663fb162101 100644 --- a/kong/templates/nginx_kong_stream.lua +++ b/kong/templates/nginx_kong_stream.lua @@ -11,8 +11,8 @@ lua_socket_log_errors off; lua_max_running_timers 4096; lua_max_pending_timers 16384; lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; -> if lua_ssl_trusted_certificate then -lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}'; +> if lua_ssl_trusted_certificate_combined then +lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; > end lua_shared_dict stream_kong 5m; diff --git a/kong/tools/utils.lua b/kong/tools/utils.lua index 57039610e22d..2a6b1cf9e1ba 100644 --- a/kong/tools/utils.lua +++ b/kong/tools/utils.lua @@ -12,6 +12,8 @@ local ffi = require "ffi" local uuid = require "resty.jit-uuid" local pl_stringx = require "pl.stringx" local pl_stringio = require "pl.stringio" +local pl_utils = require "pl.utils" +local pl_path = require "pl.path" local zlib = require "ffi-zlib" local C = ffi.C @@ -109,8 +111,6 @@ function _M.get_hostname() end do - local pl_utils = require "pl.utils" - local _system_infos function _M.get_system_infos() @@ -136,6 +136,33 @@ do end end +do + local trusted_certs_paths = { + "/etc/ssl/certs/ca-certificates.crt", -- Debian/Ubuntu/Gentoo + "/etc/pki/tls/certs/ca-bundle.crt", -- Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", -- OpenSUSE + "/etc/pki/tls/cacert.pem", -- OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", -- CentOS/RHEL 7 + "/etc/ssl/cert.pem", -- OpenBSD, Alpine + } + + function _M.get_system_trusted_certs_filepath() + for _, path in ipairs(trusted_certs_paths) do + if pl_path.exists(path) then + return path + end + end + + return nil, + "Could not find trusted certs file in " .. + "any of the `system`-predefined locations. " .. + "Please install a certs file there or set " .. + "lua_ssl_trusted_certificate to an " .. + "specific filepath instead of `auto`" + end +end + + local get_rand_bytes do diff --git a/spec/01-unit/03-conf_loader_spec.lua b/spec/01-unit/03-conf_loader_spec.lua index ba14b5a56c6f..67b1571a0912 100644 --- a/spec/01-unit/03-conf_loader_spec.lua +++ b/spec/01-unit/03-conf_loader_spec.lua @@ -1,6 +1,8 @@ local conf_loader = require "kong.conf_loader" +local utils = require "kong.tools.utils" local helpers = require "spec.helpers" local tablex = require "pl.tablex" +local pl_path = require "pl.path" local function search_directive(tbl, directive_name, directive_value) @@ -741,6 +743,41 @@ describe("Configuration loader", function() assert.contains("lua_ssl_trusted_certificate: no such file at /path/cert.pem", errors) assert.is_nil(conf) end) + it("accepts several CA certs in lua_ssl_trusted_certificate, setting lua_ssl_trusted_certificate_combined", function() + local conf, _, errors = conf_loader(nil, { + lua_ssl_trusted_certificate = "spec/fixtures/kong_spec.crt,spec/fixtures/kong_clustering.crt", + }) + assert.is_nil(errors) + assert.same({ + pl_path.abspath("spec/fixtures/kong_spec.crt"), + pl_path.abspath("spec/fixtures/kong_clustering.crt"), + }, conf.lua_ssl_trusted_certificate) + assert.matches(".ca_combined", conf.lua_ssl_trusted_certificate_combined) + end) + it("expands the `system` property in lua_ssl_trusted_certificate", function() + local old_gstcf = utils.get_system_trusted_certs_filepath + local old_exists = pl_path.exists + finally(function() + utils.get_system_trusted_certs_filepath = old_gstcf + pl_path.exists = old_exists + end) + local system_path = "spec/fixtures/kong_spec.crt" + utils.get_system_trusted_certs_filepath = function() + return system_path + end + pl_path.exists = function(path) + return path == system_path or old_exists(path) + end + + local conf, _, errors = conf_loader(nil, { + lua_ssl_trusted_certificate = "system", + }) + assert.is_nil(errors) + assert.same({ + pl_path.abspath(system_path), + }, conf.lua_ssl_trusted_certificate) + assert.matches(".ca_combined", conf.lua_ssl_trusted_certificate_combined) + end) it("resolves SSL cert/key to absolute path", function() local conf, err = conf_loader(nil, { ssl_cert = "spec/fixtures/kong_spec.crt", diff --git a/spec/01-unit/04-prefix_handler_spec.lua b/spec/01-unit/04-prefix_handler_spec.lua index 3dd30c24e446..0429215ad86e 100644 --- a/spec/01-unit/04-prefix_handler_spec.lua +++ b/spec/01-unit/04-prefix_handler_spec.lua @@ -206,14 +206,20 @@ describe("NGINX conf compiler", function() local kong_nginx_conf = prefix_handler.compile_kong_conf(conf) assert.not_matches("lua_ssl_trusted_certificate", kong_nginx_conf, nil, true) end) - it("sets lua_ssl_trusted_certificate", function() + it("sets lua_ssl_trusted_certificate to a combined file (single entry)", function() local conf = assert(conf_loader(helpers.test_conf_path, { lua_ssl_trusted_certificate = "spec/fixtures/kong_spec.crt", })) local kong_nginx_conf = prefix_handler.compile_kong_conf(conf) - assert.matches("lua_ssl_trusted_certificate%s+.*spec/fixtures/kong_spec%.key", kong_nginx_conf) + assert.matches("lua_ssl_trusted_certificate%s+.*ca_combined", kong_nginx_conf) + end) + it("sets lua_ssl_trusted_certificate to a combined file (multiple entries)", function() + local conf = assert(conf_loader(helpers.test_conf_path, { + lua_ssl_trusted_certificate = "spec/fixtures/kong_clustering_ca.crt,spec/fixtures/kong_clustering.crt", + })) + local kong_nginx_conf = prefix_handler.compile_kong_conf(conf) + assert.matches("lua_ssl_trusted_certificate%s+.*ca_combined", kong_nginx_conf) end) - it("defines the client_max_body_size by default", function() local conf = assert(conf_loader(nil, {})) local nginx_conf = prefix_handler.compile_kong_conf(conf) diff --git a/spec/01-unit/05-utils_spec.lua b/spec/01-unit/05-utils_spec.lua index 76e377b5c369..4bb678be4b44 100644 --- a/spec/01-unit/05-utils_spec.lua +++ b/spec/01-unit/05-utils_spec.lua @@ -1,4 +1,5 @@ local utils = require "kong.tools.utils" +local pl_path = require "pl.path" describe("Utils", function() @@ -25,6 +26,40 @@ describe("Utils", function() end) end) + describe("get_system_trusted_certs_filepath()", function() + local old_exists = pl_path.exists + after_each(function() + pl_path.exists = old_exists + end) + local tests = { + Debian = "/etc/ssl/certs/ca-certificates.crt", + Fedora = "/etc/pki/tls/certs/ca-bundle.crt", + OpenSuse = "/etc/ssl/ca-bundle.pem", + OpenElec = "/etc/pki/tls/cacert.pem", + CentOS = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", + Alpine = "/etc/ssl/cert.pem", + } + + for distro, test_path in pairs(tests) do + it("retrieves the default filepath in " .. distro, function() + pl_path.exists = function(path) + return path == test_path + end + assert.same(test_path, utils.get_system_trusted_certs_filepath()) + end) + end + + it("errors if file is somewhere else", function() + pl_path.exists = function(path) + return path == "/some/unknown/location.crt" + end + + local ok, err = utils.get_system_trusted_certs_filepath() + assert.is_nil(ok) + assert.matches("Could not find trusted certs file", err) + end) + end) + describe("is_valid_uuid()", function() it("validates UUIDs from jit-uuid", function() assert.True (utils.is_valid_uuid("cbb297c0-a956-486d-ad1d-f9b42df9465a")) diff --git a/spec/02-integration/05-proxy/27-lua-ssl-trusted-cert_spec.lua b/spec/02-integration/05-proxy/27-lua-ssl-trusted-cert_spec.lua new file mode 100644 index 000000000000..2abf6500d35e --- /dev/null +++ b/spec/02-integration/05-proxy/27-lua-ssl-trusted-cert_spec.lua @@ -0,0 +1,78 @@ +local helpers = require "spec.helpers" + +for _, strategy in helpers.each_strategy() do + local bp + + describe("lua_ssl_trusted_cert #" .. strategy, function() + before_each(function() + bp = helpers.get_db_utils(strategy, { + "routes", + "plugins", + }) + + local r = bp.routes:insert({ hosts = {"test.dev"} }) + + bp.plugins:insert({ + name = "pre-function", + route = { id = r.id }, + config = { + access = { + string.format([[ + local tcpsock = ngx.socket.tcp() + assert(tcpsock:connect("%s", %d)) + + assert(tcpsock:sslhandshake( + nil, -- reused_session + nil, -- server_name + true -- ssl_verify + )) + + assert(tcpsock:close()) + ]], + helpers.mock_upstream_ssl_host, + helpers.mock_upstream_ssl_port + ) + }, + }, + }) + end) + + after_each(function() + assert(helpers.stop_kong()) + end) + + it("works with single entry", function() + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/kong_spec.crt", + })) + + local proxy_client = helpers.proxy_client() + + local res = proxy_client:get("/", { + headers = { host = "test.dev" }, + }) + assert.res_status(200, res) + end) + + it("works with multiple entries", function() + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + lua_ssl_trusted_certificate = "spec/fixtures/kong_clustering_ca.crt,spec/fixtures/kong_clustering.crt", + ssl_cert = "spec/fixtures/kong_clustering.crt", + ssl_cert_key = "spec/fixtures/kong_clustering.key", + })) + + local proxy_client = helpers.proxy_client() + + local res = proxy_client:get("/", { + headers = { host = "test.dev" }, + }) + assert.res_status(200, res) + end) + end) +end + + diff --git a/spec/fixtures/1.2_custom_nginx.template b/spec/fixtures/1.2_custom_nginx.template index e4f8e2adf3bb..b55856b5f78b 100644 --- a/spec/fixtures/1.2_custom_nginx.template +++ b/spec/fixtures/1.2_custom_nginx.template @@ -56,8 +56,8 @@ http { lua_shared_dict kong_cassandra 5m; > end lua_socket_log_errors off; -> if lua_ssl_trusted_certificate then - lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}'; +> if lua_ssl_trusted_certificate_combined then + lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; > end lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; diff --git a/spec/fixtures/custom_nginx.template b/spec/fixtures/custom_nginx.template index 265204dd20d3..e553de43aedc 100644 --- a/spec/fixtures/custom_nginx.template +++ b/spec/fixtures/custom_nginx.template @@ -29,8 +29,8 @@ http { lua_max_running_timers 4096; lua_max_pending_timers 16384; lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; -> if lua_ssl_trusted_certificate then - lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}'; +> if lua_ssl_trusted_certificate_combined then + lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; > end lua_shared_dict kong 5m; @@ -732,8 +732,8 @@ stream { lua_max_running_timers 4096; lua_max_pending_timers 16384; lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; -> if lua_ssl_trusted_certificate then - lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}'; +> if lua_ssl_trusted_certificate_combined then + lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; > end lua_shared_dict stream_kong 5m;