local helpers = require "spec.helpers" local constants = require "kong.constants" local pl_file = require("pl.file") local ssl_fixtures = require "spec.fixtures.ssl" local cjson = require "cjson" local fmt = string.format local kong_exec = helpers.kong_exec local read_file = helpers.file.read local PREFIX = helpers.test_conf.prefix local SOCKET_PATH = helpers.test_conf.socket_path local TEST_CONF = helpers.test_conf local TEST_CONF_PATH = helpers.test_conf_path local function wait_until_healthy(prefix) prefix = prefix or PREFIX local socket_path = prefix .. "/sockets" local cmd -- use `kong-health` if available if helpers.path.exists(helpers.bin_path .. "-health") then cmd = fmt("%s-health -p %q", helpers.bin_path, prefix) else cmd = fmt("%s health -p %q", helpers.bin_path, prefix) end assert .with_timeout(10) .eventually(function() local ok, code, stdout, stderr = helpers.execute(cmd, true) if not ok then return nil, { code = code, stdout = stdout, stderr = stderr } end return true end) .is_truthy("expected `" .. cmd .. "` to succeed") local conf = assert(helpers.get_running_conf(prefix)) if conf.proxy_listen and conf.proxy_listen ~= "off" then helpers.wait_for_file("socket", socket_path .. "/" .. constants.SOCKETS.WORKER_EVENTS) end if conf.stream_listen and conf.stream_listen ~= "off" then helpers.wait_for_file("socket", socket_path .. "/" .. constants.SOCKETS.STREAM_WORKER_EVENTS) end if conf.admin_listen and conf.admin_listen ~= "off" then local port = assert(conf.admin_listen:match(":([0-9]+)")) assert .with_timeout(10) .eventually(function() local client = helpers.admin_client(1000, port) local res, err = client:send({ path = "/status", method = "GET" }) if res then res:read_body() end client:close() if not res then return nil, err end if res.status ~= 200 then return nil, res end return true end) .is_truthy("/status API did not return 200") end end for _, strategy in helpers.each_strategy() do describe("kong start/stop #" .. strategy, function() local proxy_client, admin_client lazy_setup(function() helpers.get_db_utils(strategy) -- runs migrations helpers.prepare_prefix() end) after_each(function() if proxy_client then proxy_client:close() end if admin_client then admin_client:close() end helpers.stop_kong() end) lazy_teardown(function() helpers.stop_kong() end) it("fails with referenced values that are not initialized", function() local ok, stderr, stdout = kong_exec("start", { prefix = PREFIX, database = strategy, nginx_proxy_real_ip_header = "{vault://env/ipheader}", pg_database = TEST_CONF.pg_database, vaults = "env", }) assert.matches("vault://env/ipheader", stderr, nil, true) assert.matches("Error: failed to dereference '{vault://env/ipheader}'", stderr) assert.is_nil(stdout) assert.is_false(ok) end) it("fails to read referenced secrets when vault does not exist", function() local ok, stderr, stdout = kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_password = "{vault://non-existent/pg_password}", pg_database = TEST_CONF.pg_database, }) assert.matches("Error: failed to dereference", stderr, nil, true) assert.is_nil(stdout) assert.is_false(ok) end) it("resolves referenced secrets", function() helpers.clean_logfile() helpers.setenv("PG_PASSWORD", "dummy") local _, stderr, stdout = assert(kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_password = "{vault://env/pg_password}", pg_database = TEST_CONF.pg_database, vaults = "env", })) assert.not_matches("failed to dereference {vault://env/pg_password}", stderr, nil, true) assert.logfile().has.no.line("[warn]", true) assert.logfile().has.no.line("env/pg_password", true) assert.matches("Kong started", stdout, nil, true) assert(kong_exec("stop", { prefix = PREFIX, })) end) it("start help", function() local _, stderr = kong_exec "start --help" assert.not_equal("", stderr) end) it("stop help", function() local _, stderr = kong_exec "stop --help" assert.not_equal("", stderr) end) it("start/stop gracefully with default conf/prefix", function() assert(kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_database = TEST_CONF.pg_database, })) assert(kong_exec("stop", { prefix = PREFIX })) end) it("start/stop stops without error when references cannot be resolved", function() helpers.setenv("PG_PASSWORD", "dummy") local _, stderr, stdout = assert(kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_password = "{vault://env/pg_password}", pg_database = TEST_CONF.pg_database, vaults = "env", })) assert.not_matches("failed to dereference {vault://env/pg_password}", stderr, nil, true) assert.matches("Kong started", stdout, nil, true) helpers.unsetenv("PG_PASSWORD") local _, stderr, stdout = assert(kong_exec("stop", { prefix = PREFIX, })) assert.not_matches("failed to dereference {vault://env/pg_password}", stderr, nil, true) assert.matches("Kong stopped", stdout, nil, true) end) it("start/stop custom Kong conf/prefix", function() assert(kong_exec("start --conf " .. TEST_CONF_PATH)) assert(kong_exec("stop --prefix " .. PREFIX)) end) it("stop honors custom Kong prefix higher than environment variable", function() assert(kong_exec("start --conf " .. TEST_CONF_PATH)) helpers.setenv("KONG_PREFIX", "/tmp/dne") finally(function() helpers.unsetenv("KONG_PREFIX") end) assert(kong_exec("stop --prefix " .. PREFIX)) end) it("start/stop Kong with only stream listeners enabled", function() assert(kong_exec("start ", { prefix = PREFIX, admin_listen = "off", proxy_listen = "off", stream_listen = "127.0.0.1:9022", })) wait_until_healthy() assert(kong_exec("stop", { prefix = PREFIX })) end) it("start dumps Kong config in prefix", function() assert(kong_exec("start --conf " .. TEST_CONF_PATH)) assert.truthy(helpers.path.exists(TEST_CONF.kong_env)) end) it("should not add [emerg], [alert], [crit], [error] or [warn] lines to error log", function() helpers.clean_logfile() assert(helpers.kong_exec("start ", { prefix = helpers.test_conf.prefix, stream_listen = "127.0.0.1:9022", status_listen = "0.0.0.0:8100", })) ngx.sleep(0.1) -- wait unix domain socket assert(helpers.kong_exec("stop", { prefix = helpers.test_conf.prefix })) assert.logfile().has.no.line("[emerg]", true) assert.logfile().has.no.line("[alert]", true) assert.logfile().has.no.line("[crit]", true) assert.logfile().has.no.line("[error]", true) assert.logfile().has.no.line("[warn]", true) end) it("creates prefix directory if it doesn't exist", function() finally(function() -- this test uses a non-default prefix, so it must manage -- its kong instance directly helpers.stop_kong("foobar") end) assert.falsy(helpers.path.exists("foobar")) assert(kong_exec("start --prefix foobar", { pg_database = TEST_CONF.pg_database, })) assert.truthy(helpers.path.exists("foobar")) end) describe("verbose args", function() it("accepts verbose --v", function() local _, _, stdout = assert(kong_exec("start --v --conf " .. TEST_CONF_PATH)) assert.matches("[verbose] prefix in use: ", stdout, nil, true) end) it("accepts debug --vv", function() local _, _, stdout = assert(kong_exec("start --vv --conf " .. TEST_CONF_PATH)) assert.matches("[verbose] prefix in use: ", stdout, nil, true) assert.matches("[debug] prefix = ", stdout, nil, true) assert.matches("[debug] database = ", stdout, nil, true) end) it("prints ENV variables when detected #postgres", function() local _, _, stdout = assert(kong_exec("start --vv --conf " .. TEST_CONF_PATH, { database = "postgres", admin_listen = "127.0.0.1:8001" })) assert.matches('KONG_DATABASE ENV found with "postgres"', stdout, nil, true) assert.matches('KONG_ADMIN_LISTEN ENV found with "127.0.0.1:8001"', stdout, nil, true) end) it("prints config in alphabetical order", function() local _, _, stdout = assert(helpers.kong_exec("start --vv --conf " .. TEST_CONF_PATH)) assert.matches("admin_listen.*anonymous_reports.*pg_user.*prefix.*", stdout) end) it("does not print sensitive settings in config", function() local _, _, stdout = assert(kong_exec("start --vv --conf " .. TEST_CONF_PATH, { pg_password = "do not print", })) assert.matches('KONG_PG_PASSWORD ENV found with "******"', stdout, nil, true) assert.matches('pg_password = "******"', stdout, nil, true) end) end) describe("custom --nginx-conf", function() local templ_fixture = "spec/fixtures/custom_nginx.template" it("accept a custom Nginx configuration", function() assert(kong_exec("start --conf " .. TEST_CONF_PATH .. " --nginx-conf " .. templ_fixture)) assert.truthy(helpers.path.exists(TEST_CONF.nginx_conf)) local contents = read_file(TEST_CONF.nginx_conf) assert.matches("# This is a custom nginx configuration template for Kong specs", contents, nil, true) assert.matches("daemon on;", contents, nil, true) end) end) describe("/etc/hosts resolving in CLI", function() if strategy == "postgres" then it("resolves #postgres hostname", function() assert(kong_exec("start --conf " .. TEST_CONF_PATH, { pg_host = "localhost", database = "postgres" })) end) end end) -- TODO: update with new error messages and behavior pending("--run-migrations", function() before_each(function() helpers.dao:drop_schema() end) after_each(function() helpers.dao:drop_schema() helpers.dao:run_migrations() end) describe("errors", function() it("does not start with an empty datastore", function() local ok, stderr = kong_exec("start --conf "..TEST_CONF_PATH) assert.False(ok) assert.matches("the current database schema does not match this version of Kong.", stderr) end) it("does not start if migrations are not up to date", function() helpers.dao:run_migrations() -- Delete a migration to simulate inconsistencies between version local _, err = helpers.dao.db:query([[ DELETE FROM schema_migrations WHERE id='rate-limiting' ]]) assert.is_nil(err) local ok, stderr = kong_exec("start --conf "..TEST_CONF_PATH) assert.False(ok) assert.matches("the current database schema does not match this version of Kong.", stderr) end) it("connection check errors are prefixed with DB-specific prefix", function() local ok, stderr = kong_exec("start --conf " .. TEST_CONF_PATH, { pg_port = 99999, }) assert.False(ok) assert.matches("[" .. TEST_CONF.database .. " error]", stderr, 1, true) end) end) end) describe("nginx_main_daemon = off", function() it("redirects nginx's stdout to 'kong start' stdout", function() local stdout_path = os.tmpname() finally(function() os.remove(stdout_path) end) local cmd = fmt("KONG_PROXY_ACCESS_LOG=/dev/stdout " .. "KONG_NGINX_MAIN_DAEMON=off %s start -c %s " .. ">%s 2>/dev/null &", helpers.bin_path, TEST_CONF_PATH, stdout_path) local ok, _, _, stderr = helpers.execute(cmd, true) assert.truthy(ok, stderr) wait_until_healthy() proxy_client = helpers.proxy_client() local res = proxy_client:get("/hello") assert.res_status(404, res) -- no Route configured -- TEST: since nginx started in the foreground, the 'kong start' command -- stdout should receive all of nginx's stdout as well. assert.logfile(stdout_path).has.line([["GET /hello HTTP/1.1" 404]], true, 5) end) end) if strategy == "off" then describe("declarative config start", function() it("starts with a valid declarative config file", function() local yaml_file = helpers.make_yaml_file [[ _format_version: "1.1" services: - name: my-service url: http://127.0.0.1:15555 routes: - name: example-route hosts: - example.test ]] finally(function() os.remove(yaml_file) end) assert(helpers.start_kong({ database = "off", declarative_config = yaml_file, nginx_worker_processes = 16, -- stress test initialization nginx_conf = "spec/fixtures/custom_nginx.template", })) wait_until_healthy() proxy_client = helpers.proxy_client() local res = proxy_client:get("/", { headers = { host = "example.test" } }) assert.response(res).has.status(200) end) it("starts with a valid declarative config string", function() local config_string = cjson.encode { _format_version = "1.1", services = { { name = "my-service", url = "http://127.0.0.1:15555", routes = { { name = "example-route", hosts = { "example.test" } } } } } } assert(helpers.start_kong({ database = "off", declarative_config_string = config_string, nginx_conf = "spec/fixtures/custom_nginx.template", })) wait_until_healthy() proxy_client = helpers.proxy_client() local res = proxy_client:get("/", { headers = { host = "example.test" } }) assert.response(res).has.status(200) end) it("hash is set correctly for a non-empty configuration", function() local yaml_file = helpers.make_yaml_file [[ _format_version: "1.1" services: - name: my-service url: http://127.0.0.1:15555 routes: - name: example-route hosts: - example.test ]] finally(function() os.remove(yaml_file) end) assert(helpers.start_kong({ database = "off", declarative_config = yaml_file, nginx_conf = "spec/fixtures/custom_nginx.template", })) wait_until_healthy() admin_client = helpers.admin_client() local res = admin_client:get("/status") assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.is_string(json.configuration_hash) assert.equals(32, #json.configuration_hash) assert.not_equal(constants.DECLARATIVE_EMPTY_CONFIG_HASH, json.configuration_hash) end) it("hash is set correctly for an empty configuration", function() -- not specifying declarative_config this time assert(helpers.start_kong({ database = "off", nginx_conf = "spec/fixtures/custom_nginx.template", })) wait_until_healthy() admin_client = helpers.admin_client() local res = admin_client:get("/status") assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.is_string(json.configuration_hash) assert.equals(constants.DECLARATIVE_EMPTY_CONFIG_HASH, json.configuration_hash) end) end) end describe("errors", function() it("start inexistent Kong conf file", function() local ok, stderr = kong_exec "start --conf foobar.conf" assert.False(ok) assert.is_string(stderr) assert.matches("Error: no file at: foobar.conf", stderr, nil, true) end) it("stop inexistent prefix", function() assert(helpers.kong_exec("start --prefix " .. PREFIX, { pg_database = TEST_CONF.pg_database, })) local ok, stderr = kong_exec("stop --prefix inexistent") assert.False(ok) assert.matches("Error: no such prefix: .*/inexistent", stderr) end) it("notifies when Kong is already running", function() assert(helpers.kong_exec("start --prefix " .. PREFIX, { pg_database = TEST_CONF.pg_database, })) local ok, stderr = kong_exec("start --prefix " .. PREFIX, { pg_database = TEST_CONF.pg_database }) assert.False(ok) assert.matches("Kong is already running in " .. PREFIX, stderr, nil, true) end) it("should not start Kong if already running in prefix", function() local kill = require "kong.cmd.utils.kill" assert(helpers.kong_exec("start --prefix " .. PREFIX, { pg_database = TEST_CONF.pg_database, })) local ok, stderr = kong_exec("start --prefix " .. PREFIX, { pg_database = TEST_CONF.pg_database }) assert.False(ok) assert.matches("Kong is already running in " .. PREFIX, stderr, nil, true) assert(kill.is_running(TEST_CONF.nginx_pid)) end) it("does not prepare the prefix directory if Kong is already running", function() assert(kong_exec("start --prefix " .. PREFIX, { database = "off", nginx_main_worker_processes = "1", })) local kong_env = PREFIX .. "/.kong_env" local before, err = read_file(kong_env) assert.truthy(before, "failed reading .kong_env: " .. tostring(err)) assert.matches("nginx_main_worker_processes = 1", before) -- sanity local ok, stderr = kong_exec("start --prefix " .. PREFIX, { database = "off", nginx_main_worker_processes = "2", }) assert.falsy(ok) assert.matches("Kong is already running", stderr) local after after, err = read_file(kong_env) assert.truthy(after, "failed reading .kong_env: " .. tostring(err)) assert.equal(before, after, ".kong_env file was rewritten") end) it("ensures the required shared dictionaries are defined", function() local tmp_nginx_config = "spec/fixtures/nginx_conf.tmp" local prefix_handler = require "kong.cmd.utils.prefix_handler" local conf_loader = require "kong.conf_loader" local nginx_conf = assert(conf_loader(helpers.test_conf_path, { prefix = "servroot_tmp", })) assert(prefix_handler.prepare_prefix(nginx_conf)) assert.truthy(helpers.path.exists(nginx_conf.nginx_conf)) local kong_nginx_conf = assert(prefix_handler.compile_kong_conf(nginx_conf)) for _, dict in ipairs(constants.DICTS) do -- remove shared dictionary entry local http_cfg = string.gsub(kong_nginx_conf, "lua_shared_dict%s" .. dict .. "%s.-\n", "") local conf = [[pid pids/nginx.pid; error_log logs/error.log debug; daemon on; worker_processes 1; events { multi_accept off; } http { ]] .. http_cfg .. [[ } ]] pl_file.write(tmp_nginx_config, conf) local ok, err = helpers.start_kong({ nginx_conf = tmp_nginx_config }) assert.falsy(ok) assert.matches( "missing shared dict '" .. dict .. "' in Nginx configuration, " .. "are you using a custom template? Make sure the 'lua_shared_dict " .. dict .. " [SIZE];' directive is defined.", err, nil, true) end finally(function() os.remove(tmp_nginx_config) end) end) if strategy == "off" then it("does not start with an invalid declarative config file", function() helpers.clean_logfile() local yaml_file = helpers.make_yaml_file [[ _format_version: "1.1" services: - name: "@gobo" protocol: foo host: mockbin.org - name: my-service url: http://mockbin.org routes: - name: example-route hosts: - example.test - \\99 ]] finally(function() os.remove(yaml_file) end) local ok, err = helpers.start_kong({ database = "off", declarative_config = yaml_file, }) assert.falsy(ok) assert.matches("in 'protocol': expected one of: grpc, grpcs, http, https, tcp, tls, tls_passthrough, udp", err, nil, true) assert.matches("in 'name': invalid value '@gobo': the only accepted ascii characters are alphanumerics or ., -, _, and ~", err, nil, true) assert.matches("in entry 2 of 'hosts': invalid hostname: \\\\99", err, nil, true) end) it("dbless can reference secrets in declarative configuration", function() helpers.clean_logfile() helpers.setenv("SESSION_SECRET", "top-secret-value") local yaml_file = helpers.make_yaml_file [[ _format_version: "3.0" _transform: true plugins: - name: session instance_name: session config: secret: "{vault://mocksocket/session-secret}" ]] finally(function() helpers.unsetenv("SESSION_SECRET") os.remove(yaml_file) end) helpers.setenv("KONG_LUA_PATH_OVERRIDE", "./spec/fixtures/custom_vaults/?.lua;./spec/fixtures/custom_vaults/?/init.lua;;") helpers.get_db_utils(strategy, { "vaults", }, { "session" }, { "mocksocket" }) local ok, err = helpers.start_kong({ database = "off", declarative_config = yaml_file, vaults = "mocksocket", plugins = "session", }) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert.truthy(ok) assert.not_matches("error", err) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mocksocket/session-secret}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) assert(helpers.restart_kong({ database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mocksocket/session-secret}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.reload_kong("reload --prefix " .. helpers.test_conf.prefix, { database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mocksocket/session-secret}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) end) it("dbless does not fail fatally when referencing secrets doesn't work in declarative configuration", function() helpers.clean_logfile() local yaml_file = helpers.make_yaml_file [[ _format_version: "3.0" _transform: true plugins: - name: session instance_name: session config: secret: "{vault://mocksocket/session-secret-unknown}" ]] finally(function() os.remove(yaml_file) end) helpers.setenv("KONG_LUA_PATH_OVERRIDE", "./spec/fixtures/custom_vaults/?.lua;./spec/fixtures/custom_vaults/?/init.lua;;") helpers.get_db_utils(strategy, { "vaults", }, { "session" }, { "mocksocket" }) local ok, err = helpers.start_kong({ database = "off", declarative_config = yaml_file, vaults = "mocksocket", plugins = "session", }) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert.truthy(ok) assert.not_matches("error", err) assert.logfile().has.line(" {vault://mocksocket/session-secret-unknown}", true) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) assert(helpers.restart_kong({ database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.line(" {vault://mocksocket/session-secret-unknown}", true) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.reload_kong("reload --prefix " .. helpers.test_conf.prefix, { database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.line(" {vault://mocksocket/session-secret-unknown}", true) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) end) it("dbless can reference secrets in declarative configuration using vault entities", function() helpers.clean_logfile() helpers.setenv("SESSION_SECRET_AGAIN", "top-secret-value") local yaml_file = helpers.make_yaml_file [[ _format_version: "3.0" _transform: true plugins: - name: session instance_name: session config: secret: "{vault://mock/session-secret-again}" vaults: - description: my vault name: mocksocket prefix: mock ]] finally(function() helpers.unsetenv("SESSION_SECRET_AGAIN") os.remove(yaml_file) end) helpers.setenv("KONG_LUA_PATH_OVERRIDE", "./spec/fixtures/custom_vaults/?.lua;./spec/fixtures/custom_vaults/?/init.lua;;") helpers.get_db_utils(strategy, { "vaults", }, { "session" }, { "mocksocket" }) local ok, err = helpers.start_kong({ database = "off", declarative_config = yaml_file, vaults = "mocksocket", plugins = "session", }) assert.truthy(ok) assert.not_matches("error", err) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mock/session-secret-again}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.restart_kong({ database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mock/session-secret-again}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.reload_kong("reload --prefix " .. helpers.test_conf.prefix, { database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line(" {vault://mock/session-secret-again}", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) end) it("dbless does not fail fatally when referencing secrets doesn't work in declarative configuration using vault entities", function() helpers.clean_logfile() local yaml_file = helpers.make_yaml_file [[ _format_version: "3.0" _transform: true plugins: - name: session instance_name: session config: secret: "{vault://mock/session-secret-unknown-again}" vaults: - description: my vault name: mocksocket prefix: mock ]] finally(function() os.remove(yaml_file) end) helpers.setenv("KONG_LUA_PATH_OVERRIDE", "./spec/fixtures/custom_vaults/?.lua;./spec/fixtures/custom_vaults/?/init.lua;;") helpers.get_db_utils(strategy, { "vaults", }, { "session" }, { "mocksocket" }) local ok, err = helpers.start_kong({ database = "off", declarative_config = yaml_file, vaults = "mocksocket", plugins = "session", }) assert.truthy(ok) assert.not_matches("error", err) assert.logfile().has.line(" {vault://mock/session-secret-unknown-again}", true) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.restart_kong({ database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.line(" {vault://mock/session-secret-unknown-again}", true) assert.logfile().has.no.line("[error]", true, 0) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) assert(helpers.reload_kong("reload --prefix " .. helpers.test_conf.prefix, { database = "off", vaults = "mocksocket", plugins = "session", declarative_config = "", })) assert.logfile().has.line(" {vault://mock/session-secret-unknown-again}", true) assert.logfile().has.no.line("traceback", true, 0) assert.logfile().has.no.line("could not find vault", true, 0) proxy_client = helpers.proxy_client() local res = proxy_client:get("/") assert.res_status(404, res) local body = assert.response(res).has.jsonbody() assert.equal("no Route matched with those values", body.message) end) end end) describe("deprecated properties", function() it("deprecate ", function() local _, stderr, _ = assert(kong_exec("start", { prefix = PREFIX, worker_consistency = "strict", })) assert.matches("the configuration value 'strict' for configuration property 'worker_consistency' is deprecated", stderr, nil, true) assert.matches("the 'worker_consistency' configuration property is deprecated", stderr, nil, true) end) end) describe("socket_path", function() it("is created on demand by `kong prepare`", function() local dir, cleanup = helpers.make_temp_dir() finally(cleanup) local cmd = fmt("prepare -p %q", dir) assert.truthy(kong_exec(cmd), "expected '" .. cmd .. "' to succeed") assert.truthy(helpers.path.isdir(dir .. "/sockets"), "expected '" .. dir .. "/sockets' directory to be created") end) it("can be a user-created symlink", function() local prefix, cleanup = helpers.make_temp_dir() finally(cleanup) local socket_path socket_path, cleanup = helpers.make_temp_dir() finally(cleanup) assert.truthy(helpers.execute(fmt("ln -sf %q %q/sockets", socket_path, prefix)), "failed to symlink socket path") local preserve_prefix = true assert(helpers.start_kong({ prefix = prefix, database = "off", nginx_conf = "spec/fixtures/custom_nginx.template", }, nil, preserve_prefix)) finally(function() helpers.stop_kong(prefix) end) wait_until_healthy(prefix) assert.truthy(helpers.path.exists(socket_path .. "/" .. constants.SOCKETS.WORKER_EVENTS), "worker events socket was not created in the socket_path dir") end) end) describe("dangling socket cleanup", function() local pidfile = TEST_CONF.nginx_pid -- the worker events socket is just one of many unix sockets we use local event_sock = SOCKET_PATH .. "/" .. constants.SOCKETS.WORKER_EVENTS local env = { prefix = PREFIX, database = strategy, admin_listen = "127.0.0.1:9001", proxy_listen = "127.0.0.1:8000", stream_listen = "127.0.0.1:9022", nginx_main_worker_processes = 2, -- keeping this low for the sake of speed } local start_cmd = fmt("start -p %q -c %q", PREFIX, TEST_CONF_PATH) local function start() local ok, code, stdout, stderr = kong_exec(start_cmd, env, true) if ok then wait_until_healthy() end return ok, code, stdout, stderr end local function assert_start() local ok, code, stdout, stderr = start() assert(ok, "failed to start kong...\n" .. "exit code: " .. tostring(code) .. "\n" .. "stdout:\n" .. tostring(stdout) .. "\n" .. "stderr:\n" .. tostring(stderr) .. "\n") assert.equals(0, code) assert.matches("Kong started", stdout) return stdout, stderr end local function sigkill(pid) if type(pid) == "table" then pid = table.concat(pid, " ") end helpers.execute("kill -9 " .. pid) helpers.wait_until(function() -- kill returns: -- -- * 0 on success -- * 1 on failure -- * 64 on partial failure/success -- -- we might be passing in multiple pids, so we need to explicitly -- check the exit code is 1, otherwise one or more processes might -- still be alive local _, code = helpers.execute("kill -0 " .. pid, true) return code == 1 end) end local function get_worker_pids() local admin = assert(helpers.admin_client()) local res = admin:get("/") assert.res_status(200, res) local json = assert.response(res).has.jsonbody() admin:close() return json.pids.workers end local function kill_all() local workers = get_worker_pids() local master = assert(read_file(pidfile)) master = master:gsub("%s+", "") sigkill(master) sigkill(workers) end before_each(function() helpers.clean_prefix(PREFIX) assert_start() kill_all() assert(helpers.path.exists(event_sock), "events socket (" .. event_sock .. ") unexpectedly removed") end) it("removes unix socket files in the prefix directory", function() local _, stderr = assert_start() assert.matches("[warn] Found dangling unix sockets in the prefix directory", stderr, nil, true) assert.matches(SOCKET_PATH, stderr, nil, true) assert.matches("removing unix socket", stderr) assert.matches(event_sock, stderr, nil, true) end) it("does not log anything if Kong was stopped cleanly and no sockets are found", function() assert_start() assert(helpers.stop_kong(PREFIX, true)) local stdout, stderr = assert_start() assert.not_matches("prefix directory .*not found", stdout) assert.not_matches("[warn] Found dangling unix sockets in the prefix directory", stderr, nil, true) assert.not_matches("unix socket", stderr) end) it("does not do anything if kong is already running", function() assert_start() local ok, code, _, stderr = start() assert.falsy(ok, "expected `kong start` to fail with kong already running") assert.equals(1, code) assert.not_matches("unix socket", stderr) assert(helpers.path.exists(event_sock)) end) end) describe("docker-start", function() -- tests here are meant to emulate the behavior of `kong docker-start`, which -- is a fake CLI command found in our default docker entrypoint: -- -- https://github.com/Kong/docker-kong/blob/d588854aaeeab7ac39a0e801e9e6a1ded2f65963/docker-entrypoint.sh -- -- this is the only(?) context where nginx is typically invoked directly -- instead of first going through `kong.cmd.start`, so it has some subtle -- differences it("works with resty.events when KONG_PREFIX is a relative path", function() local prefix = "relpath" local socket_path = "relpath/sockets" finally(function() -- this test uses a non-default prefix, so it must manage -- its kong instance directly helpers.stop_kong(prefix) end) assert(kong_exec(fmt("prepare -p %q -c %q", prefix, TEST_CONF_PATH), { database = strategy, proxy_listen = "127.0.0.1:8000", stream_listen = "127.0.0.1:9000", admin_listen = "127.0.0.1:8001", })) local nginx, err = require("kong.cmd.utils.nginx_signals").find_nginx_bin() assert.is_string(nginx, err) local started started, err = helpers.execute(fmt("%s -p %q -c nginx.conf", nginx, prefix)) assert.truthy(started, "starting Kong failed: " .. tostring(err)) -- wait until everything is running wait_until_healthy(prefix) assert.truthy(helpers.path.exists(socket_path .. "/" .. constants.SOCKETS.WORKER_EVENTS)) assert.truthy(helpers.path.exists(socket_path .. "/" .. constants.SOCKETS.STREAM_WORKER_EVENTS)) local log = prefix .. "/logs/error.log" assert.logfile(log).has.no.line("[error]", true, 0) assert.logfile(log).has.no.line("[alert]", true, 0) assert.logfile(log).has.no.line("[crit]", true, 0) assert.logfile(log).has.no.line("[emerg]", true, 0) end) end) describe("start/stop with vault references ", function() it("resolve array-like configuration", function () helpers.clean_prefix(PREFIX) helpers.clean_logfile() helpers.setenv("PG_PASSWORD", "dummy") helpers.setenv("CERT", ssl_fixtures.cert) helpers.setenv("KEY", ssl_fixtures.key) finally(function() helpers.unsetenv("PG_PASSWORD") helpers.unsetenv("CERT") helpers.unsetenv("KEY") end) local _, stderr, stdout = assert(kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_password = "{vault://env/pg_password}", pg_database = TEST_CONF.pg_database, lua_ssl_trusted_certificate = "{vault://env/cert}, system", ssl_cert_key = "{vault://env/key}", ssl_cert = "{vault://env/cert}", vaults = "env", })) assert.not_matches("failed to dereference {vault://env/pg_password}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/cert}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/key}", stderr, nil, true) assert.logfile().has.no.line("[warn]", true) assert.logfile().has.no.line("bad value type", true) assert.logfile().has.no.line("env/pg_password", true) assert.logfile().has.no.line("env/cert", true) assert.logfile().has.no.line("env/key", true) assert.matches("Kong started", stdout, nil, true) assert(kong_exec("stop", { prefix = PREFIX, })) end) it("resolve secrets when both http and stream subsystem are enabled", function () helpers.clean_prefix(PREFIX) helpers.clean_logfile() helpers.setenv("PG_PASSWORD", "dummy") helpers.setenv("CERT", ssl_fixtures.cert) helpers.setenv("KEY", ssl_fixtures.key) helpers.setenv("CERT_ALT", ssl_fixtures.cert_alt) helpers.setenv("KEY_ALT", ssl_fixtures.key_alt) helpers.setenv("LOGLEVEL", "error") finally(function() helpers.unsetenv("PG_PASSWORD") helpers.unsetenv("CERT") helpers.unsetenv("KEY") helpers.unsetenv("LOGLEVEL") end) local avail_port = helpers.get_available_port() local _, stderr, stdout = assert(kong_exec("start", { prefix = PREFIX, database = TEST_CONF.database, pg_password = "{vault://env/pg_password}", pg_database = TEST_CONF.pg_database, loglevel = "{vault://env/loglevel}", lua_ssl_trusted_certificate = "{vault://env/cert}, system", ssl_cert_key = "{vault://env/key}, {vault://env/key_alt}", ssl_cert = "{vault://env/cert}, {vault://env/cert_alt}", vaults = "env", stream_listen = "127.0.0.1:" .. avail_port .. " reuseport" })) assert.not_matches("init_by_lua error", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/pg_password}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/cert}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/cert_alt}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/key}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/key_alt}", stderr, nil, true) assert.not_matches("failed to dereference {vault://env/loglevel}", stderr, nil, true) assert.logfile().has.no.line("[warn]", true) assert.logfile().has.no.line("bad value type", true) assert.logfile().has.no.line("env/pg_password", true) assert.logfile().has.no.line("env/cert", true) assert.logfile().has.no.line("env/cert_alt", true) assert.logfile().has.no.line("env/key", true) assert.logfile().has.no.line("env/key_alt", true) assert.logfile().has.no.line("env/loglevel", true) assert.matches("Kong started", stdout, nil, true) assert(kong_exec("stop", { prefix = PREFIX, })) end) end) end) end