Skip to content

Commit

Permalink
feat(conf) add support to configure nginx directives via kong.conf
Browse files Browse the repository at this point in the history
Problem
-------

Kong ships with an NGINX template which is rendered when Kong starts.
There exists no mechanisms to add/update arbitrary NGINX directives to
the `nginx.conf` used to run Kong. To change or add any directive, user
has to use a custom NGINX template which has to be synced with Kong for
a release which introduces changes to Kong's template.  Including
options in `kong.conf` to configure NGINX directives is not a good
solution since the list will be endless. This problem can be seen in
 #3010, #3323, and #3382.

Proposed Solution
-----------------

Proposed in #3382:

There needs to be a flexible  way to specify any NGINX directive via
Kong's config file without Kong needing to maintain a list of all NGINX
directives. While a clean and ideal solution would be #2355, this commit
adopts a simpler approach as described in #3382, and keeps solutions
similar to the one proposed in #2675 possible (by way of injecting an
`include` directive).

NGINX directives can be specified using config variables with prefixes,
which helps determine the block in which to place a directive. E.g.:

* `nginx_proxy_add_header=Header-Name header-value` will add a `add_header
  Header-Name header-value;` directive in the proxy `server` block of
  Kong.

* `nginx_http_lua_shared_dict=custom_cache 2k` will add a
  `lua_shared_dict custom_cache 2k;` directive to `http` block of Kong.
  • Loading branch information
hbagdi authored and thibaultcha committed Jun 13, 2018
1 parent dcb2536 commit ac0f3f6
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 5 deletions.
1 change: 1 addition & 0 deletions kong-0.13.1-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ build = {
["kong.cmd.version"] = "kong/cmd/version.lua",
["kong.cmd.utils.log"] = "kong/cmd/utils/log.lua",
["kong.cmd.utils.kill"] = "kong/cmd/utils/kill.lua",
["kong.cmd.utils.env"] = "kong/cmd/utils/env.lua",
["kong.cmd.utils.nginx_signals"] = "kong/cmd/utils/nginx_signals.lua",
["kong.cmd.utils.prefix_handler"] = "kong/cmd/utils/prefix_handler.lua",

Expand Down
38 changes: 38 additions & 0 deletions kong/cmd/utils/env.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
local pl_utils = require "pl.utils"
local log = require "kong.cmd.utils.log"
local fmt = string.format


local cmd = [[ printenv ]]


local function read_all()
log.debug("reading environment variables: %s", cmd)

local vars = {}
local success, ret_code, stdout, stderr = pl_utils.executeex(cmd)
if not success or ret_code ~= 0 then
return nil, fmt("could not read environment variables (exit code: %d): %s",
ret_code, stderr)
end

for line in stdout:gmatch("[^\r\n]+") do
local i = string.find(line, "=") -- match first =

if i then
local k = string.sub(line, 1, i - 1)
local v = string.sub(line, i + 1)

if k and v then
vars[k] = v
end
end
end

return vars
end


return {
read_all = read_all,
}
20 changes: 20 additions & 0 deletions kong/cmd/utils/nginx_signals.lua
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ function _M.start(kong_conf)
return true
end

function _M.check_conf(kong_conf)
local nginx_bin, err = _M.find_nginx_bin()
if not nginx_bin then
return nil, err
end

local cmd = fmt("KONG_NGINX_CONF_CHECK=true %s -t -p %s -c %s",
nginx_bin, kong_conf.prefix, "nginx.conf")

log.debug("testing nginx configuration: %s", cmd)

local ok, retcode, _, stderr = pl_utils.executeex(cmd)
if not ok then
return nil, ("nginx configuration is invalid " ..
"(exit code %d):\n%s"):format(retcode, stderr)
end

return true
end

function _M.stop(kong_conf)
return send_signal(kong_conf, "TERM")
end
Expand Down
11 changes: 9 additions & 2 deletions kong/cmd/utils/prefix_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local log = require "kong.cmd.utils.log"
local constants = require "kong.constants"
local ffi = require "ffi"
local fmt = string.format
local nginx_signals = require "kong.cmd.utils.nginx_signals"

local function gen_default_ssl_cert(kong_config, admin)
-- create SSL folder
Expand Down Expand Up @@ -194,8 +195,8 @@ local function prepare_prefix(kong_config, nginx_custom_template_path)
return nil, err
end
end
if not pl_path.exists(kong_config.nginx_admin_acc_logs) then
local ok, err = pl_file.write(kong_config.nginx_admin_acc_logs, "")
if not pl_path.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
end
Expand Down Expand Up @@ -254,6 +255,12 @@ local function prepare_prefix(kong_config, nginx_custom_template_path)
end
pl_file.write(kong_config.nginx_kong_conf, nginx_kong_conf)

-- testing written NGINX conf
local ok, err = nginx_signals.check_conf(kong_config)
if not ok then
return nil, err
end

-- write kong.conf in prefix (for workers and CLI)
local buf = {
"# *************************",
Expand Down
79 changes: 78 additions & 1 deletion kong/conf_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local pl_path = require "pl.path"
local tablex = require "pl.tablex"
local utils = require "kong.tools.utils"
local log = require "kong.cmd.utils.log"
local env = require "kong.cmd.utils.env"
local ip = require "kong.tools.ip"
local ciphers = require "kong.tools.ciphers"

Expand All @@ -28,11 +29,17 @@ local header_key_to_name = {
[string.lower(headers.UPSTREAM_STATUS)] = headers.UPSTREAM_STATUS,
}

local DYNAMIC_KEY_PREFIXES = {
["nginx_http_directives"] = "nginx_http_",
["nginx_proxy_directives"] = "nginx_proxy_",
["nginx_admin_directives"] = "nginx_admin_",
}

local PREFIX_PATHS = {
nginx_pid = {"pids", "nginx.pid"},
nginx_err_logs = {"logs", "error.log"},
nginx_acc_logs = {"logs", "access.log"},
nginx_admin_acc_logs = {"logs", "admin_access.log"},
admin_acc_logs = {"logs", "admin_access.log"},
nginx_conf = {"nginx.conf"},
nginx_kong_conf = {"nginx-kong.conf"}
;
Expand Down Expand Up @@ -448,6 +455,26 @@ local function parse_listeners(values)
return list
end

local function parse_nginx_directives(dyn_key_prefix, conf)
conf = conf or {}
local directives = {}

for k, v in pairs(conf) do
if type(k) == "string" then
local directive = string.match(k, dyn_key_prefix .. "(.+)")
if directive then
if tonumber(v) then
v = string.format("%q", v)
end

directives[directive] = v
end
end
end

return directives
end

--- Load Kong configuration
-- The loaded configuration will have all properties from the default config
-- merged with the (optionally) specified config file, environment variables
Expand Down Expand Up @@ -514,6 +541,51 @@ local function load(path, custom_conf)
-- Merging & validation
-----------------------

-- find dynamic keys that need to be loaded
do
local dynamic_keys = {}

local function find_dynamic_keys(dyn_key_prefix, t)
t = t or {}

for k, v in pairs(t) do
local directive = string.match(k, "(" .. dyn_key_prefix .. ".+)")
if directive then
dynamic_keys[directive] = true
t[k] = tostring(v)
end
end
end

local kong_env_vars = {}

-- get env vars prefixed with KONG_<dyn_key_prefix>
do
local env_vars, err = env.read_all()
if err then
return nil, err
end

for k, v in pairs(env_vars) do
local kong_var = string.match(string.lower(k), "^kong_(.+)")
if kong_var then
-- the value will be read in `overrides()`
kong_env_vars[kong_var] = true
end
end
end

for _, dyn_key_prefix in pairs(DYNAMIC_KEY_PREFIXES) do
find_dynamic_keys(dyn_key_prefix, custom_conf)
find_dynamic_keys(dyn_key_prefix, kong_env_vars)
find_dynamic_keys(dyn_key_prefix, from_file_conf)
end

-- union (add dynamic keys to `defaults` to prevent removal of the keys
-- during the intersection that happens later)
defaults = tablex.merge(defaults, dynamic_keys, true)
end

-- merge default conf with file conf, ENV variables and arg conf (with precedence)
local conf = tablex.pairmap(overrides, defaults, from_file_conf, custom_conf)

Expand All @@ -525,6 +597,11 @@ local function load(path, custom_conf)

conf = tablex.merge(conf, defaults) -- intersection (remove extraneous properties)

-- nginx directives from conf
for directives_block, dyn_key_prefix in pairs(DYNAMIC_KEY_PREFIXES) do
conf[directives_block] = parse_nginx_directives(dyn_key_prefix, conf)
end

-- print alphabetically-sorted values
do
local conf_arr = {}
Expand Down
7 changes: 7 additions & 0 deletions kong/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ do
"directive is defined.")
end
end

-- if we're running `nginx -t` then don't initialize
if os.getenv("KONG_NGINX_CONF_CHECK") then
return {
init = function() end,
}
end
end

require("kong.globalpatches")()
Expand Down
15 changes: 15 additions & 0 deletions kong/templates/nginx_kong.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE}}';
lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}};
> end
# injected nginx_http_* directives
> for k, v in pairs(nginx_http_directives) do
$(k) $(v);
> end
init_by_lua_block {
kong = require 'kong'
kong.init()
Expand Down Expand Up @@ -101,6 +106,11 @@ server {
set_real_ip_from $(trusted_ips[i]);
> end
# injected nginx_proxy_* directives
> for k, v in pairs(nginx_proxy_directives) do
$(k) $(v);
> end
location / {
set $upstream_host '';
set $upstream_upgrade '';
Expand Down Expand Up @@ -180,6 +190,11 @@ server {
ssl_ciphers ${{SSL_CIPHERS}};
> end
# injected nginx_admin_* directives
> for k, v in pairs(nginx_admin_directives) do
$(k) $(v);
> end
location / {
default_type application/json;
content_by_lua_block {
Expand Down
36 changes: 35 additions & 1 deletion spec/01-unit/002-conf_loader_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ describe("Configuration loader", function()
assert.equal("/usr/local/kong/pids/nginx.pid", conf.nginx_pid)
assert.equal("/usr/local/kong/logs/error.log", conf.nginx_err_logs)
assert.equal("/usr/local/kong/logs/access.log", conf.nginx_acc_logs)
assert.equal("/usr/local/kong/logs/admin_access.log", conf.nginx_admin_acc_logs)
assert.equal("/usr/local/kong/logs/admin_access.log", conf.admin_acc_logs)
assert.equal("/usr/local/kong/nginx.conf", conf.nginx_conf)
assert.equal("/usr/local/kong/nginx-kong.conf", conf.nginx_kong_conf)
assert.equal("/usr/local/kong/.kong_env", conf.kong_env)
Expand Down Expand Up @@ -171,6 +171,40 @@ describe("Configuration loader", function()
assert.equal("test#123", conf.pg_password)
end)

describe("dynamic directives", function()
it("loads flexible prefix based configs from a file", function()
local conf = assert(conf_loader("spec/fixtures/nginx-directives.conf"))

assert.equal("custom_cache 5m",
conf.nginx_http_directives["lua_shared_dict"])

assert.equal("8 24k",
conf.nginx_http_directives["large_client_header_buffers"])
end)

it("quotes numeric flexible prefix based configs", function()
local conf, err = conf_loader(nil, {
["nginx_http_max_pending_timers"] = 4096,
})
assert.is_nil(err)

assert.equal([["4096"]], conf.nginx_http_directives["max_pending_timers"])
end)

it("accepts flexible config values with precedence", function()
local conf = assert(conf_loader("spec/fixtures/nginx-directives.conf", {
["nginx_http_large_client_header_buffers"] = "4 16k",
["nginx_http_lua_shared_dict"] = "custom_cache 2m",
}))

assert.equal("custom_cache 2m",
conf.nginx_http_directives["lua_shared_dict"])

assert.equal("4 16k",
conf.nginx_http_directives["large_client_header_buffers"])
end)
end)

describe("nginx_user", function()
it("is nil by default", function()
local conf = assert(conf_loader(helpers.test_conf_path))
Expand Down
2 changes: 1 addition & 1 deletion spec/01-unit/003-prefix_handler_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ describe("NGINX conf compiler", function()
assert.truthy(exists(tmp_config.nginx_kong_conf))
assert.truthy(exists(tmp_config.nginx_err_logs))
assert.truthy(exists(tmp_config.nginx_acc_logs))
assert.truthy(exists(tmp_config.nginx_admin_acc_logs))
assert.truthy(exists(tmp_config.admin_acc_logs))
end)
it("dumps Kong conf", function()
assert(prefix_handler.prepare_prefix(tmp_config))
Expand Down
9 changes: 9 additions & 0 deletions spec/02-integration/02-cmd/09-prepare_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,14 @@ describe("kong prepare", function()
assert.is_string(stderr)
assert.matches("Error: no file at: foobar.conf", stderr, nil, true)
end)

it("on invalid nginx directive", function()
local ok, stderr = helpers.kong_exec("prepare --conf spec/fixtures/invalid_nginx_directives.conf" ..
" -p " .. TEST_PREFIX)
assert.False(ok)
assert.is_string(stderr)
assert.matches("[emerg] unknown directive \"random_directive\"", stderr,
nil, true)
end)
end)
end)
Loading

0 comments on commit ac0f3f6

Please sign in to comment.