Files
2026-07-13 12:32:21 +08:00

1356 lines
44 KiB
Lua

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 <worker_consistency>", 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