diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f04cb3fbee1..0605879d386b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,14 @@ ## Unreleased +### Additions + +#### Core +- Allow `kong.conf` ssl properties to be stored in vaults or environment + variables. Allow such properties to be configured directly as content + or base64 encoded content. + [#9253](https://github.com/Kong/kong/pull/9253) + ### Fixes #### Core diff --git a/kong.conf.default b/kong.conf.default index 2e0b0e5c6ff3..274b39d11773 100644 --- a/kong.conf.default +++ b/kong.conf.default @@ -261,7 +261,7 @@ # DP node, but issued by a cluster-wide # common CA certificate: `cluster_ca_cert`. -#cluster_cert = # Filename of the cluster certificate to use +#cluster_cert = # Cluster certificate to use # when establishing secure communication # between control and data plane nodes. # You can use the `kong hybrid` command to @@ -270,8 +270,14 @@ # for all nodes. Under `pki` mode it # should be a different certificate for each # DP node. + # + # The certificate can be configured on this + # property with either of the following values: + # * absolute path to the certificate + # * certificate content + # * base64 encoded certificate content -#cluster_cert_key = # Filename of the cluster certificate key to +#cluster_cert_key = # Cluster certificate key to # use when establishing secure communication # between control and data plane nodes. # You can use the `kong hybrid` command to @@ -280,6 +286,12 @@ # for all nodes. Under `pki` mode it # should be a different certificate for each # DP node. + # + # The certificate key can be configured on this + # property with either of the following values: + # * absolute path to the certificate key + # * certificate key content + # * base64 encoded certificate key content #cluster_ca_cert = # The trusted CA certificate file in PEM # format used for Control Plane to verify @@ -294,6 +306,12 @@ # # This field is ignored if `cluster_mtls` is # set to `shared`. + # + # The certificate can be configured on this property + # with either of the following values: + # * absolute path to the certificate + # * certificate content + # * base64 encoded certificate content #------------------------------------------------------------------------------ # HYBRID MODE DATA PLANE @@ -654,8 +672,9 @@ #ssl_dhparam = # Defines DH parameters for DHE ciphers from the # predefined groups: `ffdhe2048`, `ffdhe3072`, - # `ffdhe4096`, `ffdhe6144`, `ffdhe8192`, or - # from the absolute path to a parameters file. + # `ffdhe4096`, `ffdhe6144`, `ffdhe8192`, + # from the absolute path to a parameters file, or + # directly from the parameters content. # # This value is ignored if `ssl_cipher_suite` # is `modern` or `intermediate`. The reason is @@ -680,8 +699,7 @@ # # See http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout -#ssl_cert = # Comma-separated list of the absolute path to the certificates for - # `proxy_listen` values with TLS enabled. +#ssl_cert = # Comma-separated list of certificates for `proxy_listen` values with TLS enabled. # # If more than one certificates are specified, it can be used to provide # alternate type of certificate (for example, ECC certificate) that will be served @@ -692,9 +710,14 @@ # Unless this option is explicitly set, Kong will auto-generate # a pair of default certificates (RSA + ECC) first time it starts up and use # it for serving TLS requests. + # + # Certificates can be configured on this property with either of the following + # values: + # * absolute path to the certificate + # * certificate content + # * base64 encoded certificate content -#ssl_cert_key = # Comma-separated list of the absolute path to the keys for - # `proxy_listen` values with TLS enabled. +#ssl_cert_key = # Comma-separated list of keys for `proxy_listen` values with TLS enabled. # # If more than one certificate was specified for `ssl_cert`, then this # option should contain the corresponding key for all certificates @@ -703,40 +726,54 @@ # Unless this option is explicitly set, Kong will auto-generate # a pair of default private keys (RSA + ECC) first time it starts up and use # it for serving TLS requests. + # + # Keys can be configured on this property with either of the following + # values: + # * absolute path to the certificate key + # * certificate key content + # * base64 encoded certificate key content #client_ssl = off # Determines if Nginx should attempt to send client-side # TLS certificates and perform Mutual TLS Authentication # with upstream service when proxying requests. -#client_ssl_cert = # If `client_ssl` is enabled, the absolute - # path to the client certificate for the `proxy_ssl_certificate` directive. +#client_ssl_cert = # If `client_ssl` is enabled, the client certificate + # for the `proxy_ssl_certificate` directive. # # This value can be overwritten dynamically with the `client_certificate` # attribute of the `Service` object. + # + # The certificate can be configured on this property with either of the following + # values: + # * absolute path to the certificate + # * certificate content + # * base64 encoded certificate content -#client_ssl_cert_key = # If `client_ssl` is enabled, the absolute - # path to the client TLS key for the `proxy_ssl_certificate_key` directive. +#client_ssl_cert_key = # If `client_ssl` is enabled, the client TLS key + # for the `proxy_ssl_certificate_key` directive. # # This value can be overwritten dynamically with the `client_certificate` # attribute of the `Service` object. + # + # The certificate key can be configured on this property with either of the following + # values: + # * absolute path to the certificate key + # * certificate key content + # * base64 encoded certificate key content -#admin_ssl_cert = # Comma-separated list of the absolute path to the certificates for - # `admin_listen` values with TLS enabled. +#admin_ssl_cert = # Comma-separated list of certificates for `admin_listen` values with TLS enabled. # # See docs for `ssl_cert` for detailed usage. -#admin_ssl_cert_key = # Comma-separated list of the absolute path to the keys for - # `admin_listen` values with TLS enabled. +#admin_ssl_cert_key = # Comma-separated list of keys for `admin_listen` values with TLS enabled. # # See docs for `ssl_cert_key` for detailed usage. -#status_ssl_cert = # Comma-separated list of the absolute path to the certificates for - # `status_listen` values with TLS enabled. +#status_ssl_cert = # Comma-separated list of certificates for `status_listen` values with TLS enabled. # # See docs for `ssl_cert` for detailed usage. -#status_ssl_cert_key = # Comma-separated list of the absolute path to the keys for - # `status_listen` values with TLS enabled. +#status_ssl_cert_key = # Comma-separated list of keys for `status_listen` values with TLS enabled. # # See docs for `ssl_cert_key` for detailed usage. @@ -1492,8 +1529,8 @@ # https://github.com/openresty/lua-nginx-module -#lua_ssl_trusted_certificate = system # Comma-separated list of paths to certificate - # authority files for Lua cosockets in PEM format. +#lua_ssl_trusted_certificate = system # Comma-separated list of certificate authorities + # for Lua cosockets in PEM format. # # The special value `system` attempts to search for the # "usual default" provided by each distro, according @@ -1515,6 +1552,13 @@ # are enabled, these certificate authority files will be # used for verifying Kong's database connections. # + # Certificates can be configured on this property + # with either of the following values: + # * `system` + # * absolute path to the certificate + # * certificate content + # * base64 encoded certificate content + # # See https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate #lua_ssl_verify_depth = 1 # Sets the verification depth in the server diff --git a/kong/cmd/utils/prefix_handler.lua b/kong/cmd/utils/prefix_handler.lua index e4e7e69b1049..605c34d78fba 100644 --- a/kong/cmd/utils/prefix_handler.lua +++ b/kong/cmd/utils/prefix_handler.lua @@ -23,15 +23,18 @@ local nginx_signals = require "kong.cmd.utils.nginx_signals" local getmetatable = getmetatable +local makepath = pl_dir.makepath local tonumber = tonumber local tostring = tostring local assert = assert local string = string +local exists = pl_path.exists local ipairs = ipairs local pairs = pairs local table = table local type = type local math = math +local join = pl_path.join local io = io local os = os @@ -65,16 +68,16 @@ local function gen_default_dhparams(kong_config) end if pem then - local ssl_path = pl_path.join(kong_config.prefix, "ssl") - if not pl_path.exists(ssl_path) then - local ok, err = pl_dir.makepath(ssl_path) + local ssl_path = join(kong_config.prefix, "ssl") + if not exists(ssl_path) then + local ok, err = makepath(ssl_path) if not ok then return nil, err end end - local param_file = pl_path.join(ssl_path, name .. ".pem") - if not pl_path.exists(param_file) then + local param_file = join(ssl_path, name .. ".pem") + if not exists(param_file) then log.verbose("generating %s DH parameters", name) local fd = assert(io.open(param_file, "w+b")) assert(fd:write(pem)) @@ -89,7 +92,7 @@ end local function gen_default_ssl_cert(kong_config, target) -- create SSL folder - local ok, err = pl_dir.makepath(pl_path.join(kong_config.prefix, "ssl")) + local ok, err = makepath(join(kong_config.prefix, "ssl")) if not ok then return nil, err end @@ -109,7 +112,7 @@ local function gen_default_ssl_cert(kong_config, target) ssl_cert_key = kong_config["ssl_cert_key_default" .. suffix] end - if not pl_path.exists(ssl_cert) and not pl_path.exists(ssl_cert_key) then + if not exists(ssl_cert) and not exists(ssl_cert_key) then log.verbose("generating %s SSL certificate (", ssl_cert, ") and key (", ssl_cert_key, ") for ", target or "proxy", " listener") local key @@ -180,8 +183,22 @@ local function gen_default_ssl_cert(kong_config, target) end -local function gen_trusted_certs_combined_file(combined_filepath, paths) +local function write_ssl_cert(path, ssl_cert) + local fd = assert(io.open(path, "w+b")) + assert(fd:write(ssl_cert)) + fd:close() +end + +local function write_ssl_cert_key(path, ssl_cert_key) + pre_create_private_file(path) + local fd = assert(io.open(path, "w+b")) + assert(fd:write(ssl_cert_key)) + fd:close() +end + + +local function gen_trusted_certs_combined_file(combined_filepath, paths) log.verbose("generating trusted certs combined file in ", combined_filepath) @@ -377,9 +394,9 @@ end local function prepare_prefix(kong_config, nginx_custom_template_path, skip_write, write_process_secrets) log.verbose("preparing nginx prefix directory at %s", kong_config.prefix) - if not pl_path.exists(kong_config.prefix) then + if not exists(kong_config.prefix) then log("prefix directory %s not found, trying to create it", kong_config.prefix) - local ok, err = pl_dir.makepath(kong_config.prefix) + local ok, err = makepath(kong_config.prefix) if not ok then return nil, err end @@ -389,26 +406,26 @@ local function prepare_prefix(kong_config, nginx_custom_template_path, skip_writ -- create directories in prefix for _, dir in ipairs {"logs", "pids"} do - local ok, err = pl_dir.makepath(pl_path.join(kong_config.prefix, dir)) + local ok, err = makepath(join(kong_config.prefix, dir)) if not ok then return nil, err end end -- create log files in case they don't already exist - if not pl_path.exists(kong_config.nginx_err_logs) then + if not exists(kong_config.nginx_err_logs) then local ok, err = pl_file.write(kong_config.nginx_err_logs, "") if not ok then return nil, err end end - if not pl_path.exists(kong_config.nginx_acc_logs) then + if not exists(kong_config.nginx_acc_logs) then local ok, err = pl_file.write(kong_config.nginx_acc_logs, "") if not ok then return nil, err end end - if not pl_path.exists(kong_config.admin_acc_logs) then + if not exists(kong_config.admin_acc_logs) then local ok, err = pl_file.write(kong_config.admin_acc_logs, "") if not ok then return nil, err @@ -448,6 +465,131 @@ local function prepare_prefix(kong_config, nginx_custom_template_path, skip_writ end end + -- create certs files and assign paths when they are passed as content + do + + local function set_dhparam_path(path) + if kong_config["nginx_http_ssl_dhparam"] then + kong_config["nginx_http_ssl_dhparam"] = path + end + + if kong_config["nginx_stream_ssl_dhparam"] then + kong_config["nginx_stream_ssl_dhparam"] = path + end + + for _, directive in ipairs(kong_config["nginx_http_directives"]) do + if directive.name == "ssl_dhparam" and directive.value then + directive.value = path + end + end + + for _, directive in ipairs(kong_config["nginx_stream_directives"]) do + if directive.name == "ssl_dhparam" and directive.value then + directive.value = path + end + end + end + + local function is_predefined_dhgroup(group) + if type(group) ~= "string" then + return false + end + + return not not openssl_pkey.paramgen({ + type = "DH", + group = group, + }) + end + + -- ensure the property value is a "content" (not a path), + -- write the content to a file and set the path in the configuration + local function write_content_set_path( + contents, + format, + write_func, + ssl_path, + target, + config_key + ) + if type(contents) == "string" then + if not exists(contents) then + if not exists(ssl_path) then + makepath(ssl_path) + end + local path = join(ssl_path, target .. format) + write_func(path, contents) + kong_config[config_key] = path + if target == "ssl-dhparam" then + set_dhparam_path(path) + end + end + + elseif type(contents) == "table" then + for i, content in ipairs(contents) do + if not exists(content) then + if not exists(ssl_path) then + makepath(ssl_path) + end + local path = join(ssl_path, target .. "-" .. i .. format) + write_func(path, content) + contents[i] = path + end + end + end + end + + local ssl_path = join(kong_config.prefix, "ssl") + for _, target in ipairs({ + "proxy", + "admin", + "status", + "client", + "cluster", + "lua-ssl-trusted", + "cluster-ca" + }) do + local cert_name + local key_name + local ssl_cert + local ssl_key + + if target == "proxy" then + cert_name = "ssl_cert" + key_name = "ssl_cert_key" + elseif target == "cluster" then + cert_name = target .. "_cert" + key_name = target .. "_cert_key" + elseif target == "cluster-ca" then + cert_name = "cluster_ca_cert" + elseif target == "lua-ssl-trusted" then + cert_name = "lua_ssl_trusted_certificate" + else + cert_name = target .. "_ssl_cert" + key_name = target .. "_ssl_cert_key" + end + + ssl_cert = cert_name and kong_config[cert_name] + ssl_key = key_name and kong_config[key_name] + + if ssl_cert and #ssl_cert > 0 then + write_content_set_path(ssl_cert, ".crt", write_ssl_cert, ssl_path, + target, cert_name) + end + + if ssl_key and #ssl_key > 0 then + write_content_set_path(ssl_key, ".key", write_ssl_cert_key, ssl_path, + target, key_name) + end + end + + local dhparam_value = kong_config["ssl_dhparam"] + if dhparam_value and not is_predefined_dhgroup(dhparam_value) then + write_content_set_path(dhparam_value, ".pem", write_ssl_cert, ssl_path, + "ssl-dhparam", "ssl_dhparam") + end + end + + if kong_config.lua_ssl_trusted_certificate_combined then gen_trusted_certs_combined_file( kong_config.lua_ssl_trusted_certificate_combined, @@ -470,7 +612,7 @@ local function prepare_prefix(kong_config, nginx_custom_template_path, skip_writ -- compile Nginx configurations local nginx_template if nginx_custom_template_path then - if not pl_path.exists(nginx_custom_template_path) then + if not exists(nginx_custom_template_path) then return nil, "no such file: " .. nginx_custom_template_path end local read_err diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index 9d004438e16f..d97c3895f69c 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -4,6 +4,7 @@ local require = require local kong_default_conf = require "kong.templates.kong_defaults" local process_secrets = require "kong.cmd.utils.process_secrets" local openssl_pkey = require "resty.openssl.pkey" +local openssl_x509 = require "resty.openssl.x509" local pl_stringio = require "pl.stringio" local pl_stringx = require "pl.stringx" local constants = require "kong.constants" @@ -17,6 +18,7 @@ local utils = require "kong.tools.utils" local log = require "kong.cmd.utils.log" local env = require "kong.cmd.utils.env" local ffi = require "ffi" +local base64 = require "ngx.base64" local fmt = string.format @@ -43,6 +45,8 @@ local abspath = pl_path.abspath local tostring = tostring local tonumber = tonumber local setmetatable = setmetatable +local decode_base64 = ngx.decode_base64 +local decode_base64url = base64.decode_base64url local get_phase do @@ -621,6 +625,35 @@ local function infer_value(value, typ, opts) end +local function decode_base64_str(str) + if type(str) == "string" then + return decode_base64(str) + or decode_base64url(str) + or nil, "base64 decoding failed: invalid input" + + else + return nil, "base64 decoding failed: not a string" + end +end + + +local function try_decode_base64(value) + if type(value) == "table" then + for i, v in ipairs(value) do + value[i] = decode_base64_str(v) or v + end + + return value + end + + if type(value) == "string" then + return decode_base64_str(value) or value + end + + return value +end + + -- Validate properties (type/enum/custom) and infer their type. -- @param[type=table] conf The configuration table to treat. local function check_and_infer(conf, opts) @@ -747,50 +780,74 @@ local function check_and_infer(conf, opts) end if ssl_cert then - for _, cert in ipairs(ssl_cert) do + for i, cert in ipairs(ssl_cert) do if not exists(cert) then - errors[#errors + 1] = prefix .. "ssl_cert: no such file at " .. cert + cert = try_decode_base64(cert) + ssl_cert[i] = cert + local _, err = openssl_x509.new(cert) + if err then + errors[#errors + 1] = prefix .. "ssl_cert: failed loading certificate from " .. cert + end end end + conf[prefix .. "ssl_cert"] = ssl_cert end if ssl_cert_key then - for _, cert_key in ipairs(ssl_cert_key) do + for i, cert_key in ipairs(ssl_cert_key) do if not exists(cert_key) then - errors[#errors + 1] = prefix .. "ssl_cert_key: no such file at " .. cert_key + cert_key = try_decode_base64(cert_key) + ssl_cert_key[i] = cert_key + local _, err = openssl_pkey.new(cert_key) + if err then + errors[#errors + 1] = prefix .. "ssl_cert_key: failed loading key from " .. cert_key + end end end + conf[prefix .. "ssl_cert_key"] = ssl_cert_key end end end if conf.client_ssl then - if conf.client_ssl_cert and not conf.client_ssl_cert_key then + local client_ssl_cert = conf.client_ssl_cert + local client_ssl_cert_key = conf.client_ssl_cert_key + + if client_ssl_cert and not client_ssl_cert_key then errors[#errors + 1] = "client_ssl_cert_key must be specified" - elseif conf.client_ssl_cert_key and not conf.client_ssl_cert then + elseif client_ssl_cert_key and not client_ssl_cert then errors[#errors + 1] = "client_ssl_cert must be specified" end - if conf.client_ssl_cert and not exists(conf.client_ssl_cert) then - errors[#errors + 1] = "client_ssl_cert: no such file at " .. - conf.client_ssl_cert + if client_ssl_cert and not exists(client_ssl_cert) then + client_ssl_cert = try_decode_base64(client_ssl_cert) + conf.client_ssl_cert = client_ssl_cert + local _, err = openssl_x509.new(client_ssl_cert) + if err then + errors[#errors + 1] = "client_ssl_cert: failed loading certificate from " .. client_ssl_cert + end end - if conf.client_ssl_cert_key and not exists(conf.client_ssl_cert_key) then - errors[#errors + 1] = "client_ssl_cert_key: no such file at " .. - conf.client_ssl_cert_key + if client_ssl_cert_key and not exists(client_ssl_cert_key) then + client_ssl_cert_key = try_decode_base64(client_ssl_cert_key) + conf.client_ssl_cert_key = client_ssl_cert_key + local _, err = openssl_pkey.new(client_ssl_cert_key) + if err then + errors[#errors + 1] = "client_ssl_cert_key: failed loading key from " .. + client_ssl_cert_key + end end end if conf.lua_ssl_trusted_certificate then local new_paths = {} - for _, path in ipairs(conf.lua_ssl_trusted_certificate) do - if path == "system" then + for _, trusted_cert in ipairs(conf.lua_ssl_trusted_certificate) do + if trusted_cert == "system" then local system_path, err = utils.get_system_trusted_certs_filepath() if system_path then - path = system_path + trusted_cert = system_path elseif not ngx.IS_CLI then log.info("lua_ssl_trusted_certificate: unable to locate system bundle: " .. err .. @@ -799,12 +856,18 @@ local function check_and_infer(conf, opts) end end - if path ~= "system" then - if not exists(path) then - errors[#errors + 1] = "lua_ssl_trusted_certificate: no such file at " .. path + if trusted_cert ~= "system" then + if not exists(trusted_cert) then + trusted_cert = try_decode_base64(trusted_cert) + local _, err = openssl_x509.new(trusted_cert) + if err then + errors[#errors + 1] = "lua_ssl_trusted_certificate: " .. + "failed loading certificate from " .. + trusted_cert + end end - new_paths[#new_paths + 1] = path + new_paths[#new_paths + 1] = trusted_cert end end @@ -835,8 +898,19 @@ local function check_and_infer(conf, opts) end if conf.ssl_dhparam then - if not is_predefined_dhgroup(conf.ssl_dhparam) and not exists(conf.ssl_dhparam) then - errors[#errors + 1] = "ssl_dhparam: no such file at " .. conf.ssl_dhparam + if not is_predefined_dhgroup(conf.ssl_dhparam) + and not exists(conf.ssl_dhparam) then + conf.ssl_dhparam = try_decode_base64(conf.ssl_dhparam) + local _, err = openssl_pkey.new( + { + type = "DH", + param = conf.ssl_dhparam + } + ) + if err then + errors[#errors + 1] = "ssl_dhparam: failed loading certificate from " + .. conf.ssl_dhparam + end end else @@ -988,18 +1062,40 @@ local function check_and_infer(conf, opts) end if conf.role == "control_plane" or conf.role == "data_plane" then - if not conf.cluster_cert or not conf.cluster_cert_key then + local cluster_cert = conf.cluster_cert + local cluster_cert_key = conf.cluster_cert_key + local cluster_ca_cert = conf.cluster_ca_cert + + if not cluster_cert or not cluster_cert_key then errors[#errors + 1] = "cluster certificate and key must be provided to use Hybrid mode" else - if not exists(conf.cluster_cert) then - errors[#errors + 1] = "cluster_cert: no such file at " .. - conf.cluster_cert + if not exists(cluster_cert) then + cluster_cert = try_decode_base64(cluster_cert) + conf.cluster_cert = cluster_cert + local _, err = openssl_x509.new(cluster_cert) + if err then + errors[#errors + 1] = "cluster_cert: failed loading certificate from " .. cluster_cert + end end - if not exists(conf.cluster_cert_key) then - errors[#errors + 1] = "cluster_cert_key: no such file at " .. - conf.cluster_cert_key + if not exists(cluster_cert_key) then + cluster_cert_key = try_decode_base64(cluster_cert_key) + conf.cluster_cert_key = cluster_cert_key + local _, err = openssl_pkey.new(cluster_cert_key) + if err then + errors[#errors + 1] = "cluster_cert_key: failed loading key from " .. cluster_cert_key + end + end + end + + if cluster_ca_cert and not exists(cluster_ca_cert) then + cluster_ca_cert = try_decode_base64(cluster_ca_cert) + conf.cluster_ca_cert = cluster_ca_cert + local _, err = openssl_x509.new(cluster_ca_cert) + if err then + errors[#errors + 1] = "cluster_ca_cert: failed loading certificate from " .. + cluster_ca_cert end end end @@ -1690,25 +1786,29 @@ local function load(path, custom_conf, opts) if ssl_cert and ssl_cert_key then if type(ssl_cert) == "table" then for i, cert in ipairs(ssl_cert) do - ssl_cert[i] = abspath(cert) + if exists(ssl_cert[i]) then + ssl_cert[i] = abspath(cert) + end end - else + elseif exists(ssl_cert) then conf[prefix .. "_cert"] = abspath(ssl_cert) end - if type(ssl_cert) == "table" then + if type(ssl_cert_key) == "table" then for i, key in ipairs(ssl_cert_key) do - ssl_cert_key[i] = abspath(key) + if exists(ssl_cert_key[i]) then + ssl_cert_key[i] = abspath(key) + end end - else + elseif exists(ssl_cert_key) then conf[prefix .. "_cert_key"] = abspath(ssl_cert_key) end end end - if conf.cluster_ca_cert then + if conf.cluster_ca_cert and exists(conf.cluster_ca_cert) then conf.cluster_ca_cert = abspath(conf.cluster_ca_cert) end @@ -1716,7 +1816,7 @@ local function load(path, custom_conf, opts) conf.stream_proxy_ssl_enabled or conf.admin_ssl_enabled or conf.status_ssl_enabled - + for _, name in ipairs({ "nginx_http_directives", "nginx_stream_directives" }) do for i, directive in ipairs(conf[name]) do if directive.name == "ssl_dhparam" then @@ -1728,7 +1828,7 @@ local function load(path, custom_conf, opts) remove(conf[name], i) end - else + elseif exists(directive.value) then directive.value = abspath(directive.value) end @@ -1739,8 +1839,16 @@ local function load(path, custom_conf, opts) if conf.lua_ssl_trusted_certificate and #conf.lua_ssl_trusted_certificate > 0 then - conf.lua_ssl_trusted_certificate = - tablex.map(pl_path.abspath, conf.lua_ssl_trusted_certificate) + + conf.lua_ssl_trusted_certificate = tablex.map( + function(cert) + if exists(cert) then + return abspath(cert) + end + return cert + end, + conf.lua_ssl_trusted_certificate + ) conf.lua_ssl_trusted_certificate_combined = abspath(pl_path.join(conf.prefix, ".ca_combined")) diff --git a/spec/01-unit/03-conf_loader_spec.lua b/spec/01-unit/03-conf_loader_spec.lua index f799dedd650b..7f0a8b9c7bbe 100644 --- a/spec/01-unit/03-conf_loader_spec.lua +++ b/spec/01-unit/03-conf_loader_spec.lua @@ -728,6 +728,57 @@ describe("Configuration loader", function() assert.is_nil(conf) end) describe("SSL", function() + it("accepts and decodes valid base64 values", function() + local ssl_fixtures = require "spec.fixtures.ssl" + local cert = ssl_fixtures.cert + local cacert = ssl_fixtures.cert_ca + local key = ssl_fixtures.key + local dhparam = ssl_fixtures.dhparam + + local properties = { + ssl_cert = cert, + ssl_cert_key = key, + admin_ssl_cert = cert, + admin_ssl_cert_key = key, + status_ssl_cert = cert, + status_ssl_cert_key = key, + client_ssl_cert = cert, + client_ssl_cert_key = key, + cluster_cert = cert, + cluster_cert_key = key, + cluster_ca_cert = cacert, + ssl_dhparam = dhparam, + lua_ssl_trusted_certificate = cacert + } + local conf_params = { + ssl_cipher_suite = "old", + client_ssl = "on", + role = "control_plane", + status_listen = "127.0.0.1:123 ssl", + proxy_listen = "127.0.0.1:456 ssl", + admin_listen = "127.0.0.1:789 ssl" + } + + for n, v in pairs(properties) do + conf_params[n] = ngx.encode_base64(v) + end + local conf, err = conf_loader(nil, conf_params) + + assert.is_nil(err) + assert.is_table(conf) + for name, decoded_val in pairs(properties) do + local values = conf[name] + if type(values) == "table" then + for i = 1, #values do + assert.equals(decoded_val, values[i]) + end + end + + if type(values) == "string" then + assert.equals(decoded_val, values) + end + end + end) describe("proxy", function() it("does not check SSL cert and key if SSL is off", function() local conf, err = conf_loader(nil, { @@ -770,8 +821,8 @@ describe("Configuration loader", function() ssl_cert_key = "/path/cert_key.pem" }) assert.equal(2, #errors) - assert.contains("ssl_cert: no such file at /path/cert.pem", errors) - assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("ssl_cert: failed loading certificate from /path/cert.pem", errors) + assert.contains("ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) conf, _, errors = conf_loader(nil, { @@ -779,7 +830,7 @@ describe("Configuration loader", function() ssl_cert_key = "/path/cert_key.pem" }) assert.equal(1, #errors) - assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) end) it("requires SSL DH param file to exist", function() @@ -788,7 +839,7 @@ describe("Configuration loader", function() ssl_dhparam = "/path/dhparam.pem" }) assert.equal(1, #errors) - assert.contains("ssl_dhparam: no such file at /path/dhparam.pem", errors) + assert.contains("ssl_dhparam: failed loading certificate from /path/dhparam.pem", errors) assert.is_nil(conf) conf, _, errors = conf_loader(nil, { @@ -806,7 +857,7 @@ describe("Configuration loader", function() lua_ssl_trusted_certificate = "/path/cert.pem", }) assert.equal(1, #errors) - assert.contains("lua_ssl_trusted_certificate: no such file at /path/cert.pem", errors) + assert.contains("lua_ssl_trusted_certificate: failed loading certificate from /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() @@ -865,6 +916,30 @@ describe("Configuration loader", function() }) assert.is_nil(errors) end) + it("requires cluster_cert and key files to exist", function() + local conf, _, errors = conf_loader(nil, { + role = "data_plane", + database = "off", + cluster_cert = "path/kong_clustering.crt", + cluster_cert_key = "path/kong_clustering.key", + }) + assert.equal(2, #errors) + assert.contains("cluster_cert: failed loading certificate from path/kong_clustering.crt", errors) + assert.contains("cluster_cert_key: failed loading key from path/kong_clustering.key", errors) + assert.is_nil(conf) + end) + it("requires cluster_ca_cert file to exist", function() + local conf, _, errors = conf_loader(nil, { + role = "data_plane", + database = "off", + cluster_ca_cert = "path/kong_clustering_ca.crt", + cluster_cert = "spec/fixtures/kong_clustering.crt", + cluster_cert_key = "spec/fixtures/kong_clustering.key", + }) + assert.equal(1, #errors) + assert.contains("cluster_ca_cert: failed loading certificate from path/kong_clustering_ca.crt", errors) + assert.is_nil(conf) + end) it("autoload cluster_cert or cluster_ca_cert for data plane in lua_ssl_trusted_certificate", function() local conf, _, errors = conf_loader(nil, { role = "data_plane", @@ -1050,8 +1125,8 @@ describe("Configuration loader", function() client_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(2, #errors) - assert.contains("client_ssl_cert: no such file at /path/cert.pem", errors) - assert.contains("client_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("client_ssl_cert: failed loading certificate from /path/cert.pem", errors) + assert.contains("client_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) conf, _, errors = conf_loader(nil, { @@ -1060,7 +1135,7 @@ describe("Configuration loader", function() client_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(1, #errors) - assert.contains("client_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("client_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) end) it("resolves SSL cert/key to absolute path", function() @@ -1117,8 +1192,8 @@ describe("Configuration loader", function() admin_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(2, #errors) - assert.contains("admin_ssl_cert: no such file at /path/cert.pem", errors) - assert.contains("admin_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("admin_ssl_cert: failed loading certificate from /path/cert.pem", errors) + assert.contains("admin_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) conf, _, errors = conf_loader(nil, { @@ -1126,7 +1201,7 @@ describe("Configuration loader", function() admin_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(1, #errors) - assert.contains("admin_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("admin_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) end) it("resolves SSL cert/key to absolute path", function() @@ -1188,8 +1263,8 @@ describe("Configuration loader", function() status_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(2, #errors) - assert.contains("status_ssl_cert: no such file at /path/cert.pem", errors) - assert.contains("status_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("status_ssl_cert: failed loading certificate from /path/cert.pem", errors) + assert.contains("status_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) conf, _, errors = conf_loader(nil, { @@ -1198,7 +1273,7 @@ describe("Configuration loader", function() status_ssl_cert_key = "/path/cert_key.pem" }) assert.equal(1, #errors) - assert.contains("status_ssl_cert_key: no such file at /path/cert_key.pem", errors) + assert.contains("status_ssl_cert_key: failed loading key from /path/cert_key.pem", errors) assert.is_nil(conf) end) it("resolves SSL cert/key to absolute path", function() diff --git a/spec/01-unit/04-prefix_handler_spec.lua b/spec/01-unit/04-prefix_handler_spec.lua index bb4e14efdb9b..550d15a85aeb 100644 --- a/spec/01-unit/04-prefix_handler_spec.lua +++ b/spec/01-unit/04-prefix_handler_spec.lua @@ -2,6 +2,8 @@ local helpers = require "spec.helpers" local conf_loader = require "kong.conf_loader" local prefix_handler = require "kong.cmd.utils.prefix_handler" local ffi = require "ffi" +local tablex = require "pl.tablex" +local ssl_fixtures = require "spec.fixtures.ssl" local exists = helpers.path.exists local join = helpers.path.join @@ -965,6 +967,83 @@ describe("NGINX conf compiler", function() assert.truthy(exists(join(conf.prefix, "ssl", conf.nginx_http_ssl_dhparam .. ".pem"))) assert.truthy(exists(join(conf.prefix, "ssl", conf.nginx_stream_ssl_dhparam .. ".pem"))) end) + describe("accept raw content for configuration properties", function() + it("writes files and re-configures valid paths", function() + local cert = ssl_fixtures.cert + local cacert = ssl_fixtures.cert_ca + local key = ssl_fixtures.key + local dhparam = ssl_fixtures.dhparam + + local params = { + ssl_cipher_suite = "old", + prefix = tmp_config.prefix, + } + local ssl_params = { + ssl_cert = cert, + ssl_cert_key = key, + admin_ssl_cert = cert, + admin_ssl_cert_key = key, + status_ssl_cert = cert, + status_ssl_cert_key = key, + client_ssl_cert = cert, + client_ssl_cert_key = key, + cluster_cert = cert, + cluster_cert_key = key, + cluster_ca_cert = cacert, + ssl_dhparam = dhparam, + lua_ssl_trusted_certificate = cacert + } + + local conf, err = conf_loader(nil, tablex.merge(params, ssl_params, true)) + assert(prefix_handler.prepare_prefix(conf)) + assert.is_nil(err) + assert.is_table(conf) + + for name, input_content in pairs(ssl_params) do + local paths = conf[name] + if type(paths) == "table" then + for i = 1, #paths do + assert.truthy(exists(paths[i])) + local configured_content = assert(helpers.file.read(paths[i])) + assert.equals(input_content, configured_content) + end + end + + if type(paths) == "string" then + assert.truthy(exists(paths)) + local configured_content = assert(helpers.file.read(paths)) + assert.equals(input_content, configured_content) + end + end + end) + it("sets lua_ssl_trusted_certificate to a combined file" .. + "(multiple content entries)", function() + local cacerts = string.format( + "%s,%s", + ssl_fixtures.cert_ca, + ssl_fixtures.cert_ca + ) + local conf = assert(conf_loader(nil, { + lua_ssl_trusted_certificate = cacerts, + prefix = tmp_config.prefix + })) + assert(prefix_handler.prepare_prefix(conf)) + assert.is_table(conf) + local trusted_certificates = conf["lua_ssl_trusted_certificate"] + assert.equal(2, #trusted_certificates) + local combined = assert( + helpers.file.read(conf["lua_ssl_trusted_certificate_combined"]) + ) + assert.equal( + combined, + string.format( + "%s\n%s\n", + ssl_fixtures.cert_ca, + ssl_fixtures.cert_ca + ) + ) + end) + end) end) describe("custom template", function() diff --git a/spec/fixtures/ssl.lua b/spec/fixtures/ssl.lua index 59aadde179e6..db731bc8282d 100644 --- a/spec/fixtures/ssl.lua +++ b/spec/fixtures/ssl.lua @@ -593,4 +593,13 @@ BfRWvYpS5xKcHmXg2QJxy2VpvElHLg5Y2lligEZhO+5Sm2OG/hixBmiFvEvxPEB8 XDbnPBpOQK9nicehY7oscy9yTB9Q3bUHecYLY822ueCwaJgwJWFUH+Xe4u6xIH5l A/IyIfyOqxjUc34Me+37ehNmbTIxZ1BqLddppm9QsSAD7cDMurfb3pRpju4= -----END RSA PRIVATE KEY-----]], + + dhparam = [[-----BEGIN DH PARAMETERS----- +MIIBDAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAgICAOE= +-----END DH PARAMETERS-----]], }